• Publicidad

Almacenar datos leídos de un fichero en una matriz

¿Apenas comienzas con Perl? En este foro podrás encontrar y hacer preguntas básicas de Perl con respuestas aptas a tu nivel.

Almacenar datos leídos de un fichero en una matriz

Notapor sernamar » 2008-12-15 07:31 @355

Hola,

estoy intentando hacer un script que lee un fichero en el que aparece información sobre algunos ficheros que están alojados en distintos servidores que debería después bajar mediante FTP.

El fichero es algo así:
Código: Seleccionar todo
=============================================================================
Estaciones que hay que procesar
=============================================================================

=============================================================================
SERVER    : IGS
=============================================================================
ADDRESS   : laquesea
LOGIN     : elquesea
PASSWORD  : elquesea
DATA      : directorioRemoto
=============================================================================
STATION 1 : EBRE
STATION 2 : TLSE
STATION 3 : YEBE
=============================================================================

=============================================================================
SERVER    : ICV
=============================================================================
ADDRESS   : laquesea
LOGIN     : elquesea
PASSWORD  : elquesea
DATA      : directorioRemoto
=============================================================================
STATION 1 : ALCO
STATION 2 : AYOR
STATION 3 : BORR
STATION 4 : DENI
STATION 5 : MORE
STATION 6 : TORR
STATION 7 : UTIE
STATION 8 : VCIA
=============================================================================


Para cada bloque de datos del fichero tengo el nombre del servidor, la dirección, nombre de usuario,... y finalmente los ficheros que debería descargarme (STATION 1, 2, ...).

El script que he hecho para leer dicho fichero es el siguiente:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#usr/bin/perl

use strict;
use warnings;

my $inputFile = "STAT.INP";

#abrir fichero de entrada
open( INP, '<', $inputFile )
  or die("Can't read input file $inputFile: $!\n");

my $linea   = "";
my $cadena  = "";
my @ftpData = ();

while (<INP>) {

        if (/^SERVER/) {

                #leer el nombre del servidor
                $cadena = Extract2ndString( $_, ':\s' );
                print "\n|$cadena|\n";
                push( @ftpData, $cadena );

                $linea = <INP>;    #leer linea vacia

                #leer la direccion del servidor
                $linea = <INP>;
                $cadena = Extract2ndString( $linea, ':\s' );
                print "|$cadena|\n";
                push( @ftpData, $cadena );

                #leer el nombre de usuario
                $linea = <INP>;
                $cadena = Extract2ndString( $linea, ':\s' );
                print "|$cadena|\n";
                push( @ftpData, $cadena );

                #leer la contraseña de usuario
                $linea = <INP>;
                $cadena = Extract2ndString( $linea, ':\s' );
                print "|$cadena|\n";
                push( @ftpData, $cadena );

                #leer el directorio donde se encuentran los datos
                $linea = <INP>;
                $cadena = Extract2ndString( $linea, ':\s' );
                print "|$cadena|\n";
                push( @ftpData, $cadena );
               
                $linea = <INP>;    #leer linea vacia

                #leer todas las estaciones
                while ( ( $linea = <INP> ) =~ /^STATIONS/ ) {

                        $cadena = Extract2ndString( $linea, ':\s' );
                        print "|$cadena|\n";
                        push( @ftpData, $cadena );

                }

        }

}

#cerrar fichero de entrada
close INP;

print "\n\n@ftpData\n";

# ==========================================================================
# SUBRUTINAS
# ==========================================================================

sub Extract2ndString {

# Funcion : Extract2ndString
#
# Descrip : Dada una cadena (que es una linea leida) y un caracter a buscar, extrae la subcadena que hay desde el caracter a
#buscar hasta el final de la linea, eliminando el caracter nueva linea (\n)
#
# Recibe  : - linea, incluido el carater nueva linea
#           - caracter a buscar
#
# Devuelve: - subcandena
#
# Fecha   : 10/12/2008

        #my ( $linea, $caracter ) = @_;
        my $linea    = shift;
        my $caracter = shift;

        chomp($linea);
        my ( $string1, $string2 ) = split /$caracter/, $linea;

        return $string2;

}
Coloreado en 0.005 segundos, usando GeSHi 1.0.8.4


De esta forma obtengo en el array @ftpData toda la información

Pero lo que me gustaría es obtener una matriz en la que la primera fila contuviera los datos de un servidor, en la segunda los de otro, y así con todos los servidores que pudieran haber en el fichero.

De esta manera podría pasar dicha matriz a una subrutina que descargara los datos mediante FTP fila a fila, es decir, que cogiera la primera fila y descargara los ficheros (las STATIONS) desde la posición 5 de la fila hasta el final de la misma, ya que el número de ficheros a descargar no es el mismo para cada fila.

El problema que tengo es que no sé cómo hacer esto, he leído en el libro "Beginning Perl" que eso lo debería hacer mediante referencias, pero entre que soy novato, que lo estoy leyendo demasiado deprisa y mi pobre inglés, no me ha quedado nada claro.

Era por si me podíais indicar dónde encontrar información al respecto.

Además, aunque el script que he hecho funciona a la hora de leer ese fichero, no sé si habría una forma mejor, más clara, más eficiente, ... o lo que sea, de hacerlo, es que no me acaba de convencer del todo...

Un saludo, y gracias.
Última edición por sernamar el 2008-12-18 07:42 @362, editado 2 veces en total
sernamar
Perlero nuevo
Perlero nuevo
 
Mensajes: 8
Registrado: 2008-11-01 04:33 @231

Publicidad

Notapor explorer » 2008-12-15 11:18 @512

¿Se puede cambiar 'ligeramente' el formato del fichero de configuración?
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14486
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor sernamar » 2008-12-15 11:49 @534

explorer escribiste:¿Se puede cambiar 'ligeramente' el formato del fichero de configuración?

Sí, sin ningún problema, el formato lo he impuesto yo buscando un formato tal que añadir un nuevo servidor o modificar algún dato de uno existente sea muy sencillo, con lo que el formato puede ser cualquiera (siempre buscando simplicidad para alguien ajeno al script).
sernamar
Perlero nuevo
Perlero nuevo
 
Mensajes: 8
Registrado: 2008-11-01 04:33 @231

Notapor explorer » 2008-12-15 12:07 @546

El formato del fichero es un poco extraño, porque si fuera más 'normalizado' no habría que hacer muchos líos para leerlo:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;

use Data::Dumper;

my @configuracion;
my %maquina;
my ($clave,$valor);

open CONFIG, q{<}, 'kk.txt' or die "ERROR: $!\n";

while (my $linea = <CONFIG>) {
    chomp $linea;

    if (($clave,$valor) = split /\s*:\s*/, $linea, 2) {
        next if not $valor;

        if ($clave =~ /^STATION\s+(\d+)/) {
            $maquina{STATION}[$1-1] = $valor;
        }
        else {
            $maquina{$clave}        = $valor;
            print "$clave:$valor\n";
        }
    }
    else {
        guarda_maquina() if keys %maquina;
    }
}

close CONFIG;

guarda_maquina() if keys %maquina;

print Dumper \@configuracion;

sub guarda_maquina {

    my %maquina_a_guardar = %maquina;         # Creamos un nuevo objeto donde
    push @configuracion, \%maquina_a_guardar; # guardamos los datos de la máquina leída
    %maquina = ();
}

__END__
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4
Sale:
Código: Seleccionar todo
$VAR1 = [
          {
            'PASSWORD' => 'elquesea',
            'SERVER' => 'IGS',
            'DATA' => 'directorioRemoto',
            'LOGIN' => 'elquesea',
            'ADDRESS' => 'laquesea',
            'STATION' => [
                           'EBRE',
                           'TLSE',
                           'YEBE'
                         ]
          },
          {
            'PASSWORD' => 'elquesea',
            'SERVER' => 'ICV',
            'DATA' => 'directorioRemoto',
            'LOGIN' => 'elquesea',
            'ADDRESS' => 'laquesea',
            'STATION' => [
                           'ALCO',
                           'AYOR',
                           'BORR',
                           'DENI',
                           'MORE',
                           'TORR',
                           'UTIE',
                           'VCIA'
                         ]
          }
        ];
Última edición por explorer el 2008-12-18 08:48 @408, editado 1 vez en total
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14486
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor explorer » 2008-12-15 12:32 @564

sernamar escribiste:Sí, sin ningún problema, el formato lo he impuesto yo buscando un formato tal que añadir un nuevo servidor o modificar algún dato de uno existente sea muy sencillo, con lo que el formato puede ser cualquiera (siempre buscando simplicidad para alguien ajeno al script).

Pues tomándote la palabra, lo dejamos en
Código: Seleccionar todo
#=============================================================================
# Estaciones que hay que procesar
#=============================================================================

<SERVER IGS>
        ADDRESS  = laquesea
        LOGIN    = elquesea
        PASSWORD = elquesea
        DATA     = directorioRemoto
        # =================
        STATION  = EBRE
        STATION  = TLSE
        STATION  = YEBE
        # =================
</SERVER>

<SERVER ICV>
        ADDRESS  = laquesea
        LOGIN    = elquesea
        PASSWORD = elquesea
        DATA     = directorioRemoto
        # =================
        STATION  = ALCO
        STATION  = AYOR
        STATION  = BORR
        STATION  = DENI
        STATION  = MORE
        STATION  = TORR
        STATION  = UTIE
        STATION  = VCIA
        # =================
</SERVER>

Entonces, con
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;

use Data::Dumper;
use Config::General;

my $conf = new Config::General("kk.txt");
my %config = $conf->getall;

print Dumper \%config;

print $config{SERVER}->{IGS}->{STATION}->[2], "\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

sale:
Código: Seleccionar todo
$VAR1 = {
          'SERVER' => {
                        'ICV' => {
                                   'PASSWORD' => 'elquesea',
                                   'DATA' => 'directorioRemoto',
                                   'LOGIN' => 'elquesea',
                                   'STATION' => [
                                                  'ALCO',
                                                  'AYOR',
                                                  'BORR',
                                                  'DENI',
                                                  'MORE',
                                                  'TORR',
                                                  'UTIE',
                                                  'VCIA'
                                                ],
                                   'ADDRESS' => 'laquesea'
                                 },
                        'IGS' => {
                                   'PASSWORD' => 'elquesea',
                                   'DATA' => 'directorioRemoto',
                                   'LOGIN' => 'elquesea',
                                   'STATION' => [
                                                  'EBRE',
                                                  'TLSE',
                                                  'YEBE'
                                                ],
                                   'ADDRESS' => 'laquessea'
                                 }
                      }
        };
YEBE

que es otra forma de organizar la información: en hash de hash.

Mira la última línea para que veas una forma de acceso.

Recuerda también que Data::Dumper es tu amigo.
Última edición por explorer el 2008-12-18 08:57 @414, editado 3 veces en total
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14486
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor sernamar » 2008-12-15 13:04 @586

Pues me he quedado en estado de shock :shock:, con lo complicado que me parecía a mí...

¡¡¡Mil gracias, Explorer!!!

Voy a investigar lo que hacen los módulos Data::Dumper y Config::General, y continuaré con la segunda parte del problema (lo de la descarga mediante FTP).

Un saludo
sernamar
Perlero nuevo
Perlero nuevo
 
Mensajes: 8
Registrado: 2008-11-01 04:33 @231

Notapor sernamar » 2008-12-18 05:11 @258

Bueno, una vez solucionada la primera parte del script, estoy tratando de escribir la segunda parte del mismo, que consiste básicamente en descargar mediante FTP los datos leídos en la primera parte.

Os cuento cómo va la cosa y los problemas que tengo.

En primer lugar, he añadido alguna cosa al fichero de configuración en la parte del directorio donde se encuentran los datos (entrada DATA), de manera que quedaría así:
Código: Seleccionar todo
<SERVER IGS>
        ADDRESS  = laquesea
        LOGIN    = elquesea
        PASSWORD = elquesea
        DATA     = directorioRemoto/$year/$GPSday
        # =================
        STATION  = EBRE
        STATION  = TLSE
        STATION  = YEBE
        # =================
</SERVER>

<SERVER ICV>
        ADDRESS  = lalquesea
        LOGIN    = elquesea
        PASSWORD = elquesea
        DATA     = directorioRemoto/$year/$day
        # =================
        STATION  = ALCO
        STATION  = AYOR
        STATION  = BORR
        STATION  = DENI
        STATION  = MORE
        STATION  = TORR
        STATION  = UTIE
        STATION  = VCIA
        # =================
</SERVER>

Al cambiar esto, tengo el primer problema, ya que a mi me gustaría que el script substituyera las variables $year, $GSPday y $day por los valores que le indicásemos en el script. Sin embargo, al leer el fichero, el campo DATA lo lee como "single-quoted string", por lo que no los substituye.

He probado con el comando eval() (en la línea 57 del siguiente código), pero me devuelve un error:
Código: Seleccionar todo
Use of uninitialized value $remoteDir in print at /home/scripts/bernese/readfile3.pl line 58.

El problema lo podría solucionar poniendo algún "if...elsif...else" en el código, pero creo que no es lo más adecuado, ya que si el fichero de configuración cambiara, debería cambiar también el código del script. ¿Hay alguna forma de pasar una variable de "single-quoted string" a "double-quoted string" u otra forma de solucionarlo?

Por otro lado, aunque el script que he escrito funciona bien (hace su cometido), me parece que me ha quedado un poco lioso, en especial en el acceso al hash %config, con tanto "foreach" y tanto "if", "elsif",... No sé si existirá algún módulo o alguna otra forma de acceder a %config, o si alguien tiene alguna idea de cómo hacerlo de forma más legible o simple, ya que creo que si dentro de un mes volviera a ver el script, me costaría entenderlo a mi mismo, así que imagínate a quien no lo ha escrito.

Os dejo el script para que le echéis un vistazo. Gracias.

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
1       #!/usr/bin/perl
2
3       use strict;
4       use warnings;  
5       use Data::Dumper;
6                              
7       use Config::General;
8       use Net::FTP;
9
10 #==================================================================
11 # Primera parte: lectura del fichero con las configuraciones"
12 #==================================================================
13
14      my $conf   = new Config::General("STAT.INP");
15      my %config = $conf->getall;
16
17      #print Dumper \%config;
18      #print $config{SERVER}->{IGS}->{STATION}->[2], "\n";
19
20 #==================================================================
21      # Segunda parte: descarga de los datos leídos anteriormente
22 #==================================================================
23
24      #Algunas variables que necesitaré
25      #--------------------------------
26      my ( $ftp, $localDir, $remoteDir, $localFile, $remoteFile );
27      my ( $address, $login, $password, @station );
28     
29      #Datos del dia a procesar (solo para el ejemplo)
30      #-----------------------------------------------
31      my $year   = '2008';   
32      my $day    = '20080101';
33      my $GPSday = '001';
34      $localDir = "/home//descargas";
35
36      #Aquí empieza el jaleo
37      # --------------------
38
39      foreach my $server ( keys %config ) {
40
41              foreach my $serverName ( keys %{ $config{$server} } ) {
42
43                      #print "SERVIDOR: ", $serverName, "\n";
44
45                      foreach my $value ( sort( keys %{ $config{$server}{$serverName} } ) ) {
46
47                              #print $value, "\n";
48                              if ( $value eq 'ADDRESS' ) {
49
50                                      $address = $config{$server}{$serverName}{$value};
51                                      #print "dirección: ", $address, "\n"; 
52
53                              }
54                              elsif ( $value eq 'DATA' ) {
55
56                                      $remoteDir = $config{$server}{$serverName}{$value};    
57                                      $remoteDir = eval $remoteDir;  
58                                      print "directorio: ", $remoteDir, "\n";
59
60                              }
61                              elsif ( $value eq 'LOGIN' ) {
62
63                                      $login = $config{$server}{$serverName}{$value};
64                                      #print "usuario: ", $login, "\n";
65
66                              }
67                              elsif ( $value eq 'PASSWORD' ) {
68
69                                      $password = $config{$server}{$serverName}{$value};
70                                      #print "contraseña: ", $password, "\n";
71
72                              }
73                              elsif ( $value eq 'STATION' ) {
74
75                                      #Conectarse al servidor (suponiendo que ya dispongo de address, data,...)
76                                     #-----------------------------------------------------------------------
77                                      $ftp = Net::FTP->new( $address, Debug => 1 ) or die($@);
78                                      $ftp->login( $login, $password );
79
80                                      #Cambiar al directorio donde están los datos  
81                                      #-------------------------------------------
82                                      $ftp->cwd($remoteDir);
83
84                                      #Formato de descarga de ficheros       
85                                      #-------------------------------
86                                      $ftp->binary();
87
88                                      #Descargar los ficheros
89                                      #----------------------
90
91                                      @station = @{ $config{$server}{$serverName}{$value} };
92                                      foreach (@station) {
93
94                                              #print "estacion:", $_, "\n";
95                                              $remoteFile = $_ . $GPSday . '0.08d.Z';
96                                              $localFile  = $localDir . "/" . $remoteFile;
97                                              $ftp->get( $remoteFile, $localFile );
98
99                                      }
100
101                             }
102                             else {
103
104                                     die
105                                       "El fichero de configuración contiene alguna entrada errónea"
106
107                             }
108    
109
110             }
111             #Desconectarse del servidor                                                    
112             $ftp->quit() or die($@);                                                       
113     }
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
Última edición por sernamar el 2008-12-18 07:43 @363, editado 4 veces en total
sernamar
Perlero nuevo
Perlero nuevo
 
Mensajes: 8
Registrado: 2008-11-01 04:33 @231

Notapor explorer » 2008-12-18 07:02 @334

Esta es una posible solución (no probada):
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
#!/usr/bin/perl
use strict;
use warnings;
#use diagnostics;

#use Data::Dumper;
use Config::General;
use Net::FTP;

#==================================================================
# Constantes
#==================================================================
$|++;                                       # No caché

my $localDir = "/home/sernamar/descargas";  # Directorio local

my $DEBUG = 1;                              # Depurado o no

# Datos del día a procesar (solo para el ejemplo)
my $year     = '2008';
my $day      = '20080101';
my $GPSday   = '001';

#==================================================================
# Primera parte: lectura del fichero con las configuraciones
#==================================================================

    my $configuracion = Config::General->new("STAT.INP");
    my %configuracion = $configuracion->getall();

    #print Dumper \%configuracion;

#==================================================================
# Segunda parte: descarga de los datos leídos anteriormente
#==================================================================

    for my $servidor ( keys %{$configuracion{SERVER}} ) {

        my $remoteDir = eval '"' . $configuracion{SERVER}{$servidor}{DATA} . '"';

        print "SERVIDOR: $servidor\n";
        print "$remoteDir\n";

        my $ftp = Net::FTP->new(
                    $configuracion{SERVER}{$servidor}{ADDRESS },
                    Debug => $DEBUG,
                )
                or
                        warn("ERROR en la conexión: $@\n")
                    and
                        next
                ;

        $ftp->login(
                    $configuracion{SERVER}{$servidor}{LOGIN   },
                    $configuracion{SERVER}{$servidor}{PASSWORD},
                )
                or
                        warn("ERROR en la autenticación: " . $ftp->message)
                    and
                        next
                ;

        $ftp->cwd(
                    $remoteDir
                )
                or
                        warn("ERROR en el directorio remoto $remoteDir: " . $ftp->message)
                    and
                        next
                ;

        $ftp->binary();

        for my $estacion ( @{ $configuracion{SERVER}{$servidor}{STATION} } ) {

            print "\tEstacion: $estacion : ";

            my $remoteFile = $estacion . $GPSday . '0.08d.Z';
            my $localFile  = $localDir . '/' . $remoteFile;

            $ftp->get($remoteFile,   $localFile);
            print    "$remoteFile -> $localFile\n";
        }

        $ftp->quit();
    }

__END__
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4

El eval() es un poco especial... resulta que si lo evaluamos de forma directa, Perl ve un string normal y corriente que no devuelve nada. En cambio, si lo "rodeamos" por unas comillas dobles, entonces Perl hará la interpolación de las variables que hay en su interior, que es justo lo que queremos.

Desde luego, no es algo obvio. Pero funciona. Hay que recordar que eval() necesita auténtico código Perl.

Y luego, para reducir código, quitamos todas las variables y accedemos a los datos de %configuracion de forma directa.

Aumentamos la seguridad con unos cuantos warn(), que avisan de problemas, pero que permiten continuar con el resto del trabajo. die() es un poco drástico.
Última edición por explorer el 2008-12-18 11:27 @519, editado 1 vez en total
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14486
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Notapor sernamar » 2008-12-18 10:45 @490

He probado esa solución y funciona perfectamente, así que me quedo con ella, sin duda mucho más elegante que la que había hecho yo.

Muchas gracias por los aportes y la explicaciones, sin duda me son de gran ayuda, ya que apenas llevo dos semanas leyendo e intentando utilizar perl y tengo muchísimas dudas.

En cuanto tenga un poco más de tiempo, intentaré modificar el script para que genere un fichero con un informe detallado de todo lo que está haciendo, es decir, con lo mismo que sale por consola.

Un saludo, y muchas gracias de nuevo.
sernamar
Perlero nuevo
Perlero nuevo
 
Mensajes: 8
Registrado: 2008-11-01 04:33 @231

Notapor explorer » 2008-12-18 11:32 @522

JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14486
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España


Volver a Básico

¿Quién está conectado?

Usuarios navegando por este Foro: No hay usuarios registrados visitando el Foro y 0 invitados

cron