Directorios

El Operador chdir

El operador chdir nos permite cambiar de directorio dentro de la jerarquía de directorios. Estudie el siguiente ejemplo:

lhp@nereida:~/Lperl/src/dir$ cat -n chdir.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3
     4  my $directory = shift;
     5  my $expreg = (shift or '.*');
     6  $expreg = qr/$expreg/o;
     7  chdir $directory or die "no encuentro $directory: $!";
     8  my @files = `ls -a`;
     9  
    10  for (@files) {
    11    next unless /$expreg/;
    12    chop;
    13    printf "$_\n";
    14  }
Sigue un ejemplo de ejecución:
lhp@nereida:~/Lperl/src/dir$ chdir.pl /tmp/ '\d+$'
IO-Tty-1.02
Parallel-Simple-Pipe-0.01
ssh-eSbkNq1402
v274545

Obtener el directorio actual

Use el módulo Cwd:

    use Cwd;
    my $dir = getcwd;

    use Cwd 'abs_path';
    my $abs_path = abs_path($file);

Obtener el Camino Absoluto de un Fichero

Utilice la función abs_path en Cwd para conocer el camino absoluto de un fichero o directorio:

pp2@nereida:~/src/perl/testing$ perl -wde 0
  DB<1> use Cwd 'abs_path'
  DB<2> x abs_path('add.pl')
0  '/home/pp2/src/perl/testing/add.pl'
  DB<3> x abs_path('./add.pl')
0  '/home/pp2/src/perl/testing/add.pl'
  DB<4> x abs_path('/home/pp2/src/perl/testing/add.pl')
0  '/home/pp2/src/perl/testing/add.pl'
  DB<5> x abs_path('../testing/add.pl')
0  '/home/pp2/src/perl/testing/add.pl'
  DB<6> x abs_path('../testing/') # También con directorios
0  '/home/pp2/src/perl/testing'

Los operadores opendir, readdir y closedir

Otra forma de manipular un directorio es mediante el uso de un directory handle o manejador de directorio. Podemos abrirlo con opendir, leer del mismo el siguiente nombre de fichero mediante readdir y cerrarlo con closedir.

lhp@nereida:~/Lperl/src/dir$ cat -n dirhandles.pl
     1  #!/usr/bin/perl -w
     2  use strict;
     3
     4  my $directory = shift;
     5  my $regexp = (shift or '.*');
     6  opendir DH, $directory or die "No encuentro $directory: $!";
     7  
     8  foreach my $file (readdir DH) {
     9    printf "$file\n" if $file =~ m/$regexp/o;
    10  }
    11  closedir DH;
Sigue un ejemplo de ejecución:
lhp@nereida:~/Lperl/src/dir$ dirhandles.pl /tmp/ '\d+$'
ssh-eSbkNq1402
v274545
Parallel-Simple-Pipe-0.01
IO-Tty-1.02

Lista Resumida de Operadores de Directorios

La tabla 2.3 recoge de manera sucinta algunos otros operadores para la manipulación de ficheros y directorios.




Tabla 2.3: Comandos para el manejo de directorios
rename "/tmp/old", "/tmp/new" Renombra un fichero existente
unlink "one", "two", "three" Similar a rm one two three
link "one", "two" Similar a ln one two
symlink "one", "two" Similar a ln -s one two
mkdir "one" Equivalente a mkdir one
rmdir "one" Suprime el directorio one
chmod 0755, "one", "two" Cambia los permisos
chown $uid, $gid, glob "*.o" Cambia propietario y grupo
getpwnam "casiano" devuelve el uid del usuario casiano
getgrnam "users" devuelve el gid del grupo users


Los módulos File::Basename y File::Spec

Los módulos File::Basename y File::Spec permiten el manejo de nombres de fichero de una manera portable. Veamos por ejemplo la función catfile:

lhp@nereida:~/Lperl/src/perl_networking/ch2$ perl -de 0
  DB<1> use File::Spec::Functions;
  DB<2> $x = catfile('a', 'b', 'c')
  DB<3> x $x
0  'a/b/c'
En un sistema windows $x contendría 'a\b\c'. Para saber mas, lea con detalle la documentación de los módulos File::Basename y File::Spec. Usa para ello el comando man File::Basename o bien perldoc File::Basename.

Sigue un ejemplo de uso de la función fileparse:

  DB<1> use File::Basename
  DB<2> @a = fileparse("/foo/bar/baz", qr/\.[^.]*/)
  DB<3> x @a
0  'baz'
1  '/foo/bar/'
2  ''
  DB<4> @a = fileparse("/foo/bar/baz.pm", qr/\.[^.]*/)
  DB<5> x @a
0  'baz'
1  '/foo/bar/'
2  '.pm'

Acceso Mediante glob

El operador glob permite la expansión de los comodines de la shell. Asi en una expresión como:

my @all_files = glob "*";
la variable all_files guarda ordenados alfabéticamente la lista de los nombres de los ficheros en el directorio actual.

Es posible, como indica el siguiente ejemplo utilizar varios patrones separados por espacios.

my @including_hidden = glob ".* *";

Usar glob es mas eficiente y mas portable que enviar el requerimiento a la shell del sistema usando backticks (como por ejemplo en `ls .*`).

Lectura desde Globs

Cuando en el operador de lectura <> el argumento es una cadena glob de descripción de ficheros con comodines, el globbing se produce automáticamente:

my $dir = "/home/casiano/";
my $dir_files = <$dir/* $dir/.*>;

También puede hacerse en un contexto de lista:

DB<1> @modules = <*.pm>
DB<2> p "@modules"
A.pm Abstract.pm B.pm C.pm DandC.pm Frac.pm Fraction.pm

En general, si el identificador entre ángulos es un filehandler, Perl hace un acceso a través de él, en caso contrario interpreta que se trata de una operación de globbing. Esto es asi, incluso si la lectura hace uso de un manipulador indirecto que use variables intermedias. Véase el siguiente ejemplo:

  DB<1> $f = 'yapp'
  DB<2> @f = <$f/*.yp>
  DB<3> p "@f"
yapp/aSb.yp yapp/Autoaction1.yp yapp/Calc.yp yapp/Calc2.yp 
  DB<4> open F, 'logic.pl'
  DB<5> $x = F
  DB<6> @x = <$x>
  DB<7> p "@x"
#!/usr/bin/perl -w
 $a = 4; $b = "hola"; $c = 0; $d = "";
 print $a && $b,"\n";
 print $a and $b,"\n";
 print ($a and $b),"\n";

Lectura de Directorios en Contexto de Lista

lhp@nereida:~/alu/0607$ perl -wde 0
main::(-e:1):   0
  DB<1> use List::Util qw(sum)
  DB<2> p sum(map { -s $_ } <alu*>)
163840
En este código, la lectura <alu*> se produce en un contexto de lista. Por ello el directorio completo es leído y produce una lista con todos los nombres de ficheros y directorios alu* existentes en el directorio actual. El subsecuente map produce los tamaños y la llamada a sum devuelve el tamaño total.

Contexto Booleano: Bucles con Lecturas de Directorios

En ocasiones es importante distinguir entre falsedad e indefinición, como en este ejemplo:

while ($file = <*>) {
  do_something($file);
}
En este código, en cada pasada del bucle el operador <*> produce un nuevo nombre de fichero del directorio actual. El nombre es asignado a $file. ¿Que ocurre si el nombre del fichero es "0"?. En tal caso el bucle debería tener una ''muerte'' prematura. La codificación correcta sería:
while (defined($file = <*>) {
   do_something($file);
}

Siguiendo la filosofía de ''ayudar'' al programador, las últimas versiones de Perl tiene el comportamiento ''esperado''. Esto es, detectan el uso en un while del glob e interpretan que "0" como nombre de fichero no es falso. Sin embargo en otro contexto, "0" si se interpreta como falso. Observe el siguiente ejemplo:

$ cat muerte_prematura2.pl
#!/usr/bin/perl -w

my $file;
my @a = ("uno","0","dos");

while ($file = shift @a) {
  do_something($file);
}

sub do_something {
  print "$file\n";
}
Al ejecutar obtenemos:
$ ./muerte_prematura2.pl
uno

Esta modificación de la evaluación ''normal'' de una expresión en un contexto lógico/booleano no es (del todo) privilegio exclusivo de Perl. El programador puede conseguir un efecto similar usando el módulo Contextual::Return de Damian Conway, el cual permite crear variables contextuales multitipo con mas de dos tipos:

lhp@nereida:~/Lperl/src/testing$ cat -n context1.pl
 1  #!/usr/local/bin/perl -w
 2  use strict;
 3  use Contextual::Return;
 4
 5  my $x = BOOL { 0 } NUM { 3.14 } STR { "pi" };
 6
 7  unless ($x) { warn "¡El famoso número $x (".(0+$x).") pasa a ser falso!\n" } # executed!
lhp@nereida:~/Lperl/src/testing$ context1.pl
¡El famoso número pi (3.14) pasa a ser falso!



Subsecciones
Casiano Rodríguez León
2009-10-04