Sehr grosse Directories verarbeiten

Sehr grosse Directories verarbeiten

am 02.10.2006 00:44:46 von Helmut Wollmersdorfer

Normalerweise verwende ich diesen Code:

my $DIR = 'foo/bar';
my $FILE_PATTERN = 'bla\.txt';

my @files = list_files ( $DIR, $FILE_PATTERN );

for my $dir_entry ( @files ) {
if ($dir_entry
=~ m| (\d{4}-\d\d-\d\d) [\d:]+ (.*$FILE_PATTERN.*)|) {
my $file = $2;
my $date = $1;

open (TXT, "<:encoding($ENCODING)", "$DIR/$file")
or die "Can't open: $1!";
[...]
}
}

sub list_files2 {
my (
$dir,
$file_pattern
) = @_;

local(*DIR);

open(DIR, "ls -Ul $dir|") or die "Can't open: $!";

my @files = grep {/$file_pattern/}

;
close (DIR);

return ( @files );

}

"ls -Ul" deswegen, weil ein "ls -all" so ca. ab 70.000 Files/Directory
unendlich lang dauern würde. Readdir verwende ich (AFAIR) deswegen
nicht, weil ich den Timestamp brauche, mich aber nicht mit
Datumsarithmetik herumschlagen möchte.

Nun habe ich aber auch Directories mit >250.000 Files, wo ein "ls -Ul"
einen Fehler ("Argument list too long") bringt, zumindest in meiner
Konfiguration (Ext3, Debian). Weil die Files eine Nummer enthalten (z.B.
foo-4711.txt), war mein nächster Ansatz, ganz einfach ein "ls -Ul
foo-$count". Das ist aber fürchterlich langsam (würde insgesamt Tage
dauern), weil dabei anscheinend jedesmal das gesamte Directory
durchsucht wird. Ich hätte gern einen eleganten Ansatz, damit ich nicht
sowas wie

my $pattern = 'foo-' . $count . '???' . '.txt';
open(DIR, "ls -Ul $pattern|");

machen muss, was übrigens für 1-999 auch nicht funktionieren würde.

TIA

Helmut Wollmersdorfer

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 01:40:13 von Thomas Wittek

Helmut Wollmersdorfer schrieb:
> Readdir verwende ich (AFAIR) deswegen
> nicht, weil ich den Timestamp brauche, mich aber nicht mit
> Datumsarithmetik herumschlagen möchte.

Hast du `readdir` denn mal probiert? Einen Timestamp bekommst du so:

my $mtime =3D (stat($dateiname))[9];
my ($sec, $min, $hour, $mday, $mon, $year) =3D localtime($mtime);
$mon++; $year +=3D 1900;
my $timestamp =3D sprintf("%04d-%02d-%02d %02d:%02d:%02d",
$year, $mon, $mday, $hour, $min, $sec);

Wenn es noch mehr Datumsarithmetik sein soll, gibt es da ein ganz
wunderbares Modul namens Date::Calc.

--=20
Thomas Wittek
http://gedankenkonstrukt.de/
Jabber: streawkceur@jabber.i-pobox.net

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 01:59:00 von Mirco Wahab

Thus spoke Thomas Wittek (on 2006-10-02 01:40):
> Helmut Wollmersdorfer schrieb:
> Hast du `readdir` denn mal probiert? Einen Timestamp bekommst du so:
>
> my $mtime = (stat($dateiname))[9];
> my ($sec, $min, $hour, $mday, $mon, $year) = localtime($mtime);
> $mon++; $year += 1900;
> my $timestamp = sprintf("%04d-%02d-%02d %02d:%02d:%02d",
> $year, $mon, $mday, $hour, $min, $sec);
>

Nett!

Ich hab das mal ge-cutted und ge-pasted und ein
vorläufiges "lauffähiges" Programm für Helmut gebastelt ;-)

use strict;
use warnings;

my $rpat = qr/\.txt$/; # wonach suchen
my $dir = '/home/foo/bar'; # wo suchen
my $format = "%04d-%02d-%02d %02d:%02d:%02d"; # timestamp format
my @files; # *nur* ein array!

opendir(DIRH, $dir) || die ("Ponggg: $!");
while (my $item = readdir(DIRH)) {
if( $item =~ $rpat ) {
my $mtime = (stat($dir.'/'.$item))[9]; # aufpassen: path+name
my ($s, $m, $h, $md, $mo, $yr) = localtime($mtime);
$mo += 1;
$yr += 1900;
my $stamp = sprintf($format, $yr, $mo, $md, $h, $m, $s);
push @files, [$item, $stamp];
}
}
closedir(DIRH);

print "$_->[0] ($_->[1])\n", for @files;
# oder sonstwas mit @files == [filename, datum] anstellen



Grüße & gute Nacht

M.

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 09:15:31 von Helmut Wollmersdorfer

Thomas Wittek wrote:

> Hast du `readdir` denn mal probiert?

Ja, hab ich früher in selbigem Code verwendet, bis ich den Timestamp
gebraucht habe. Dann war es ein sortiertes 'ls', das aber bei ~70.000
Files in die Knie geht, und das unsortierte 'ls' hat auch seine Grenzen.

> Einen Timestamp bekommst du so:

> my $mtime = (stat($dateiname))[9];
> my ($sec, $min, $hour, $mday, $mon, $year) = localtime($mtime);
> $mon++; $year += 1900;

Danke, die paar Zeilen sind eh nicht so kompliziert.

> Wenn es noch mehr Datumsarithmetik sein soll, gibt es da ein ganz
> wunderbares Modul namens Date::Calc.

Kenne ich und verwende ich auch anderswo. Hier aber nicht notwendig,
siehe meine Antwort auf Mircos Vorschlag.

Helmut Wollmersdorfer

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 09:51:12 von Helmut Wollmersdorfer

Mirco Wahab wrote:

> Ich hab das mal ge-cutted und ge-pasted und ein
> vorläufiges "lauffähiges" Programm für Helmut gebastelt ;-)

Danke, ich hab das mal auf meinen Stil umgeschrieben und ausprobiert:

sub list_files {
my (
$dir,
$file_pattern
) = @_;

my $start_time = time;

local(*DIR);

print "opening dir \"$dir\" for read\n" if $VERBOSE >= 2;
opendir(DIR,"$dir") or die "Can't open directory \"$dir\"\n";

print "selecting files for pattern \"$file_pattern\"\n"
if $VERBOSE >= 2;
my @files = ();
my $counter = 0;
while (my $file = readdir(DIR)) {
$counter++;
if( $file =~ $file_pattern ) {
my $mtime = (stat($dir.'/'.$file))[9];
my ($s, $m, $h, $md, $mo, $yr) = localtime($mtime);
$mo += 1;
$yr += 1900;
my $format = "%04d-%02d-%02d %02d:%02d:%02d";
my $stamp = sprintf($format, $yr, $mo, $md, $h, $m, $s);
push @files, "$stamp $file";
}
}
closedir (DIR);

print "dir entries \t [ @files ] \n" if $VERBOSE >= 3;
printf "$counter files in %.3f seconds\n",time - $start_time
if $VERBOSE >= 2;

return ( @files );
}

Die Performance ist mit
291257 files in 3.067 seconds
um mehr als den Faktor 100000 besser.

Sauberer und schonender für den Speicherbedarf wäre es natürlich, den
Timestamp erst bei der Verarbeitung der Files zu holen, etwa so:

for my $file ( @files ) {
my $date = modified_date ( $DIR, $file );
# [ File verarbeiten ]
}

Wieder so ein Beispiel dafür, dass man den Aufruf einer Shell tunlichst
vermeiden soll.

Helmut Wollmersdorfer

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 13:45:51 von Slaven Rezic

Helmut Wollmersdorfer writes:

[...]
>
> Nun habe ich aber auch Directories mit >250.000 Files, wo ein "ls
> -Ul"

Was macht "-U"? Diese ls-Option kennt mein Betriebssystem nicht
(übrigens ein weiterer Grund, auf externe Programme zu verzichten).

> einen Fehler ("Argument list too long") bringt, zumindest in meiner
> Konfiguration (Ext3, Debian).

Jedes Unix (AFAIK) hat ein Limit, wie lang Kommandozeilen werden
dürfen. Um dieses Limit zu umgehen, verwendet man typischerweise
xargs.

Gruß,
Slaven

--
Slaven Rezic - slaven rezic de

sf-upload: make batch releases on SourceForge
http://sf-upload.sf.net

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 14:13:16 von Mirco Wahab

Thus spoke Slaven Rezic (on 2006-10-02 13:45):
> Helmut Wollmersdorfer writes:
>> -Ul"
> Was macht "-U"? Diese ls-Option kennt mein Betriebssystem nicht
> (übrigens ein weiterer Grund, auf externe Programme zu verzichten).


$> ls --version

ls (coreutils) 5.2.1

$> ls --help | grep "\-U"

-U do not sort; list entries in directory order



Viele Grüße

M.

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 15:47:51 von Helmut Wollmersdorfer

Slaven Rezic wrote:

> Was macht "-U"? Diese ls-Option kennt mein Betriebssystem nicht

man ls
| -U do not sort; list entries in directory order

> (übrigens ein weiterer Grund, auf externe Programme zu verzichten).

Hast natürlich recht, aber in diesem Fall wird das sicher nur auf
Linux/Debian von mir selber eingesetzt. Müsste das eine portable
Standardlösung sein, dann würde ich das sicher nicht so hinhudeln.

> Jedes Unix (AFAIK) hat ein Limit, wie lang Kommandozeilen werden
> dürfen. Um dieses Limit zu umgehen, verwendet man typischerweise
> xargs.

Nach dieser krassen Erfahrung (und einigen ähnlichen) werde ich mir in
Zukunft dreimal überlegen, über die Shell zu gehen.

Helmut Wollmersdorfer

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 17:55:12 von hjp-usenet2

On 2006-10-01 22:44, Helmut Wollmersdorfer wrote:
> Normalerweise verwende ich diesen Code:
>
> my $DIR = 'foo/bar';
> my $FILE_PATTERN = 'bla\.txt';
>
> my @files = list_files ( $DIR, $FILE_PATTERN );
>
> for my $dir_entry ( @files ) {
> if ($dir_entry
> =~ m| (\d{4}-\d\d-\d\d) [\d:]+ (.*$FILE_PATTERN.*)|) {
^^^^^^^^^^^^^^^^^^^^^^^
Du bist Dir darüber im klaren, dass das nicht das
Standard-Datumsformat von ls ist und daher nur bei bestimmten Werten von
$LANG funktioniert?

> sub list_files2 {
> my (
> $dir,
> $file_pattern
> ) = @_;
>
> local(*DIR);
>
> open(DIR, "ls -Ul $dir|") or die "Can't open: $!";
>
> my @files = grep {/$file_pattern/}

;
> close (DIR);
>
> return ( @files );
>
> }
>
> "ls -Ul" deswegen, weil ein "ls -all" so ca. ab 70.000 Files/Directory
> unendlich lang dauern würde. Readdir verwende ich (AFAIR) deswegen
> nicht, weil ich den Timestamp brauche, mich aber nicht mit
> Datumsarithmetik herumschlagen möchte.

Das wäre für mich ein Grund readdir und stat zu verwenden. Da bekommt
man die timestamps in Sekunden seit 1970 und braucht sich nicht mit
inkonsistenten Notatationen, Zeitzonen, Sommerzeit und ähnlichen
Grauslichkeiten herumschlagen.


> Nun habe ich aber auch Directories mit >250.000 Files, wo ein "ls -Ul"
> einen Fehler ("Argument list too long") bringt, zumindest in meiner
> Konfiguration (Ext3, Debian).

Ich sehe nicht, wo der Fehler bei Deinem Code herkommen sollte. Den Fehler
bekommt man üblicherweise, wenn Environment und Commandline-Argumente
gemeinsam eine bestimmte Länge (bei Linux 128 kB) überschreiten. Nun
rufst Du aber ls nur mit einem Directory auf, d.h., die Commandline ist
kurz, und ls ruft seinerseits keine externen Programme auf. Bist Du
sicher, dass Du in Deinem echten Code (der, den Du hier gepostet hast,
kann schon deswegen nicht der echte sein, weil Du list_files aufrufst,
aber list_files2 definierst) kein Wildcard verwendest?

hp


--
_ | Peter J. Holzer | > Wieso sollte man etwas erfinden was nicht
|_|_) | Sysadmin WSR | > ist?
| | | hjp@hjp.at | Was sonst wäre der Sinn des Erfindens?
__/ | http://www.hjp.at/ | -- P. Einstein u. V. Gringmuth in desd

Re: Sehr grosse Directories verarbeiten

am 02.10.2006 21:29:15 von Helmut Wollmersdorfer

Peter J. Holzer wrote:

> Das wäre für mich ein Grund readdir und stat zu verwenden.

Habe ich jetzt eh umgestellt und damit eine generellere Lösung.

> Da bekommt
> man die timestamps in Sekunden seit 1970 und braucht sich nicht mit
> inkonsistenten Notatationen, Zeitzonen, Sommerzeit und ähnlichen
> Grauslichkeiten herumschlagen.

Dafür gäbe es Date::Calc, falls es hier auf solche Feinheiten ankommen
würde.

> Bist Du
> sicher, dass Du in Deinem echten Code (der, den Du hier gepostet hast,
> kann schon deswegen nicht der echte sein, weil Du list_files aufrufst,
> aber list_files2 definierst) kein Wildcard verwendest?

Ooops - erwischt;-)
Natürlich habe ich Wildcards verwendet, ein nackertes 'ls -Ul > temp'
rattert eh brav durch (grad ausprobiert;-).

Helmut Wollmersdorfer