Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 166 lines (129 sloc) 4.18 KB
#!/usr/bin/perl -w
=head1 NAME
dwatch -- simple monitor to watch for when things change
$ dwatch --help
$ dwatch -u grep RAW /proc/net/sockstat
dwatch is a non-interactive version of C<watch -d> -- instead of
outputting the new output with highlighting of changes, it outputs
the differences in C<diff(1)> format upon each invocation.
The last output from the command is cached, and so unlike C<watch(1)>,
comparisons can span multiple invocations. This is useful for
watching for changes across long periods of time.
=head1 SEE ALSO
L<watchdiff(1)> by the same author - a very similar utility
which doesn't do any caching.
=head1 LICENSE
Copyright (c) 2004 Adam Spiers <>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <>.
use strict;
use Digest::MD5 'md5_base64';
use File::Basename;
use Getopt::Long;
my $me = basename($0);
use vars qw($new $changes);
$changes = 0;
my %opt_defaults = (
repeat => 1,
delay => 1,
cache => "$ENV{HOME}/.$me-cache",
my %opts = (
unified => 0,
context => 0,
quiet => 0,
'unified|u', 'context|c', 'quiet|q',
'repeat|r=i', 'delay|n=i', 'cache=s',
) or usage();
usage() if $opts{help};
usage("Must specify only one of unified/context/quiet.")
if $opts{unified} + $opts{context} + $opts{quiet} > 1;
usage("Delay only makes sense with repeat")
if $opts{delay} && ! defined $opts{repeat};
usage() unless @ARGV;
my $cmd = "@ARGV";
sub usage {
warn @_, "\n\n" if @_;
die <<EOUSAGE;
Usage: $me [options] command [args]
Options (defaults in []):
-h, --help This help text.
-u, --unified Output as unified diffs
-c, --context Output as context diffs
-q, --quiet Do not output, just return exit code
-r, --repeat=N Repeat N times in a row, 0 for infinite [$opt_defaults{repeat}]
-n, --delay=N Delay N seconds in between each repeat [$opt_defaults{delay}]
--cache=DIR Cache in DIR [$opt_defaults{cache}]
--cumulative Output accumulated changes, not incrementals
Exit code is the number of changes (each repeat counts as 0 or 1),
even when a SIGINT was received.
for my $key (qw/cache delay repeat/) {
$opts{$key} = $opt_defaults{$key} unless defined $opts{$key};
my $diff_opts = $opts{unified} ? '-u ' :
$opts{context} ? '-d ' :
$opts{quiet} ? '-q ' : '';
-d $opts{cache} or mkdir $opts{cache}, 0777;
my $hash = md5_base64($cmd);
$hash =~ s!/!_!g;
my $old = "$opts{cache}/$hash";
$new = "$";
$SIG{INT} = sub { exit $changes; }; # do END block on SIGINT
my $reps_done = 0;
do {{
open(CMD, "$cmd |") or die "open($cmd |) failed: $!\n";
warn "$new already existed!" if -e $new;
open(NEW, ">$new") or die "open(>$new) failed: $!\n";
print NEW $_ while <CMD>;
if (! -e $old) {
warn "# Created $old\n";
rename $new, $old
or warn "rename($new, $old) failed: $!\n";
my $diff = `diff $diff_opts"$old" "$new" 2>&1`;
if ($diff) {
$diff =~ s!^Files \Q$old\E and \Q$new\E differ\n!!;
$diff =~ s!^--- \S+\s+!--- old !m;
$diff =~ s!^\+\+\+ \S+\s+!+++ new !m;
print $diff;
if ($opts{cumulative}) {
unlink $new or warn "unlink($new) failed: $!\n";
else {
rename $new, $old
or warn "rename($new, $old) failed: $!\n";
sleep $opts{delay} if $reps_done < $opts{repeat} or $opts{repeat} == 0;
until $reps_done == $opts{repeat};
exit $changes;
sub END {
if ($new && -e $new) {
unlink $new or warn "unlink($new) failed: $!\n";