Más de lo que es necesario o requerido es una definición de redundante.
Un archivo redundante es aquel que se encuentra dentro de un sistema de archivos de manera repetida. Los archivos redundantes tienen el mismo contenido y por consecuencia el mismo tamaño; por simplicidad no se toman en cuenta el nombre del archivo, permisos, fechas de creación, modificación o de acceso, propietario o atributos extra, únicamente, contenido.
Usando un script en shell es posible encontrar archivos redundantes dentro de un subdirectorio. Usando los archivos correspondientes al kernel de GNU/Linux, he determinado que el kernel tiene un nivel de redundancia muy bajo, y si lo medimos en bytes es de 116.86K:
% cd /usr/src/linux-2.6.11.12
% redundant
[##### ] 12.00K ./arch/arm26/nwfpe/softfloat-specialize
[##### ] 12.00K ./arch/arm/nwfpe/softfloat-specialize
[######### ] 22.90K ./Documentation/networking/wanpipe.txt
[######### ] 22.90K ./Documentation/networking/wan-router.txt
[##########] 23.53K ./arch/arm26/nwfpe/softfloat-macros
[##########] 23.53K ./arch/arm/nwfpe/softfloat-macros
116.86K total
cuando el tamaño total de dicho kernel descomprimido y sin compilar es de 189.70M:
% dufmt
[ ] 2.94K REPORTING-BUGS
[ ] 13.64K README
[ ] 17.32K usr/
[ ] 18.25K COPYING
[ ] 42.46K Makefile
[ ] 54.68K MAINTAINERS
[ ] 83.82K init/
[ ] 86.95K CREDITS
[ ] 146.32K ipc/
[ ] 420.46K lib/
[ ] 550.96K crypto/
[ ] 609.20K security/
[ ] 717.13K mm/
[ ] 907.03K scripts/
[ ] 925.61K kernel/
[ ] 6.38M Documentation/
[ ] 8.77M net/
[# ] 12.47M sound/
[# ] 16.26M fs/
[## ] 19.03M include/
[### ] 33.85M arch/
[##########] 88.45M drivers/
189.70M total
Encontrar estos archivos es de gran importancia, debido a que pueden llegar a ocupar demasiado espacio en el disco duro.
A continuación se incluye el script que realiza esta tarea, cabe mencionar que se utilizan unicamente comandos que son parte de todo sistema Unix*.
redundant
#!/bin/sh
### redundant: find redundant files within current directory
### author: "Luis Alfonso Vega Garcia" <[email protected]>
digits=15
digitsp1=$(expr $digits + 1)
minsize=10k
find -type f -size +$minsize -print0 |
xargs --null du --bytes |
sort --numeric-sort |
perl -ane '/^(\d+)\t(.*)$/ and printf "%-'$digits'd%s\n", $1, $2' |
uniq --all-repeated --repeated --check-chars $digits |
cut --characters $digitsp1- |
perl -ape 'chomp;$_.="\0"' |
xargs --null md5sum |
sort --numeric-sort |
uniq --all-repeated --repeated --check-chars 32 |
cut --characters 35- |
perl -ape 'chomp;$_.="\0"' |
dufmt --0-file-list -
Que se lee de la siguiente manera:
find -type f -size +$minsize -print0 |
Es decir, no se toman en cuenta dispositivos, directorios, fifo's, ligas y sockets. Se define previamente la variable minsize, para excluir archivos muy pequeños, pues entre menor tamaño tengan los archivos, tienden a repetirse con mayor frecuencia. He encontrado que el archivo de cero bytes se repite con mucha facilidad, sorprendente más de lo que esperaba encontrar.
xargs --null du --bytes |
sort --numeric-sort |
perl -ane '/^(\d+)\t(.*)$/ and printf "%-'$digits'd%s\n", $1, $2' |
du inconvenientemente separa el tamaño y nombre de un archivo con un tabulador. Mediante este filtro se le deja un tamaño fijo a los digitos que aparecen como primer campo, dando pauta para que siguiente comando uniq funcione apropiadamente.
uniq --all-repeated --repeated --check-chars $digits |
El sort anterior asegura que los archivos que tengan el mismo tamaño sean agrupados en líneas contiguas. Con uniq se eliminan los archivos que no se repiten en tamaño. Hay que poner especial atención en la opción --check-chars dado que de esta manera se comparan solamente los caracteres correspondientes al tamaño de cada archivo.
cut --characters $digitsp1- |
\n en null o \0.
perl -ape 'chomp;$_.="\0"' |
xargs --null md5sum |
Las sumas de control MD5 son cadenas de 32 dígitos hexadecimales que representan el contenido de cierta información, en este caso de cada archivo por separado.
sort --numeric-sort |
posible bug?
uniq --all-repeated --repeated --check-chars 32 |
cut --characters 35- |
\n en null o \0.
perl -ape 'chomp;$_.="\0"' |
dufmt --0-file-list -
En caso de no instalar el comando dufmt el cual se anexa posteriormente, la última línea puede ser reemplazada por:
xargs --null du --bytes
No cabe duda que Unix* es demasiado poderoso para realizar tareas que bajo otras plataformas y/o lenguajes sería muy difícil.
Para el lector interesado en la programación usando la línea de comandos, recomiendo ampliamente el libro El entorno de programación Unix, de Kernighan y Pike.
Incluyo el script en perl de la utilería dufmt.
#!/usr/bin/perl -w
### dufmt - print friendly disk usage
### author: "Luis Alfonso Vega Garcia" <[email protected]>
use strict;
use YAML;
use Getopt::Long;
use Pod::Usage;
# TODO: change to OO
### prototype
sub logger($);
### global
my (%opts, %data, %ecmds);
### init
%data = (
version => '0.1',
'file-list-irs' => "\n",
'0-file-list-irs' => "\0",
);
%ecmds = (
du => '/usr/bin/du',
);
### command line options
GetOptions(\%opts,
'help',
'man',
'version',
'debug',
'ignore-hidden',
'file-list=s',
'0-file-list=s',
);
pod2usage(1) if $opts{help};
pod2usage(-exitstatus => 0, -verbose => 2) if $opts{man};
print("$data{version}\n"), exit 2 if $opts{version};
main();
exit 0;
################################################################################
### subs
sub logger($)
{
local $_ = shift;
print STDERR "$_\n" if $opts{debug};
}
sub main
{
init_exp2_suffix();
init_args(@ARGV);
run_du($ecmds{du}, '--summarize', '--bytes', '--total', @{ $data{args} });
parse_lines();
format_lines();
logger Dump(\%opts);
logger Dump(\%data);
}
sub read_file
{
my ($fname, $input_record_separator) = @_;
my $inp;
open $inp, "<$fname" or die "cannot open $fname: $!";
local $/ = $input_record_separator;
@{ $data{args} } = <$inp>;
close $inp;
chomp @{ $data{args} };
}
sub init_args
{
### args as file list from file
for (qw/file-list 0-file-list/) {
if (defined $opts{$_}) {
read_file($opts{$_}, $data{"$_-irs"});
return;
}
}
my @aux;
if (@_ == 0) { ### all files in current directory
@aux = glob '*' . ($opts{'ignore-hidden'} ? '' : ' .*');
} elsif (@_ == 1 and -d $_[0]) {
@aux = glob "$_[0]/*" . ($opts{'ignore-hidden'} ? '' : " $_[0]/.*");
} else {
@aux = @_;
}
### remove '.', '..', '/.' '/..'
@{ $data{args} } = grep {!(/^\.\.?$/ or /\/\.\.?$/)} @aux;
}
sub hash_marks
{
my $bytes = shift;
my $total = shift;
my $max_marks = shift || 10;
return "" unless $total;
my $chars = $bytes / $total * $max_marks;
return sprintf "[%-${max_marks}s]", '#'x$chars;
}
sub format_lines
{
my ($name, $hm, $hr, $max_marks);
$max_marks = 10;
for (@{ $data{fprops} }) {
$hm = ' ' x ($max_marks+2);
$hm = hash_marks($_->{size}, $data{total_bytes}, $max_marks)
if $_ != $data{fprops}[-1];
$hr = bytes2human_readable($_->{size});
$name = $_->{name} . ls_file_type_suffix($_->{name});
print "$hm $hr $name\n";
}
}
sub parse_lines
{
for (@{ $data{lines} }) {
chomp;
/^(\d+)\t(.*)$/ or die "bad format: $_";
my %fprop;
$fprop{size} = $1;
$fprop{name} = $2;
push @{ $data{fprops} }, \%fprop;
}
### sort by size
@{ $data{fprops} } = sort { $a->{size} <=> $b->{size} } @{ $data{fprops} };
$data{total_bytes} = $data{fprops}[-2]{size};
}
sub run_du
{
my $pinp;
open $pinp, "-|", @_ or die "cannot open @_: $!";
@{ $data{lines} } = <$pinp>;
close $pinp;
}
sub init_exp2_suffix
{
$data{suffix} = [qw/B K M G T P/];
my $aux = 1;
for (@{ $data{suffix} }) {
push @{ $data{exp2} }, $aux;
$aux *= 1024;
}
}
sub index_of
{
my $bytes = shift;
return 0 if $bytes<=0;
for (my $i=0; $i<@{ $data{exp2} }; $i++) {
return $i-1 if $data{exp2}[$i] > $bytes;
}
die "should not reach here: too big";
}
sub bytes2human_readable
{
my $bytes = shift;
my $decimals = shift || 2;
my $digits = $decimals + 5;
my $i = index_of($bytes);
return sprintf "%${digits}.${decimals}f%s", $bytes/$data{exp2}[$i],
$data{suffix}[$i];
}
sub ls_file_type_suffix
{
# @:links -l
# /:dir -d
# |:fifos -p
# =:sockets -S
# *:exec -x
local $_ = shift;
-l and return "@";
-d and return "/";
-p and return "|";
-S and return "=";
-x and return "*";
return "";
}
################################################################################
### test data
for (qw/0 1 4 1023 1024 1025 1048575 1930874/) {
print bytes2human_readable($_, 2), "\n";
}
__END__
=head1 NAME
dufmt - print friendly disk usage
=head1 SYNOPSIS
dufmt files...
dufmt [dir] [--ignore-hidden]
dufmt [--file-list=S]
dufmt [--0-file-list=S]
=head1 DESCRIPTION
Print disk usage of files.
If no files are given, take files in current dir.
If there is just one dir, take those files in dir.
The list of files can be set from an input file.
=head1 NOTE
This program relies on B<du> command, which comes in B<coreutils> package.
=head1 OPTIONS
=over 8
=item B<--help>
Print a brief help message and exits.
=item B<--man>
Prints the manual page and exits.
=item B<--version>
Print version and exits.
=item B<--ignore-hidden>
Ignore `.*' files.
=item B<--file-list=S>
The file names come from a file list, each name per line.
=item B<--0-file-list=S>
The file names come from a file list, each name separated by a null character,
i.e. \0
=back
Luis Alfonso Vega Garcia <[email protected]>
Regresar