• Publicidad

Leer y reemplazar cadena de texto

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

Re: Leer y reemplazar cadena de texto

Notapor bvayap » 2013-06-06 02:23 @141

Hola explorer,

Ya lo intenté pero no me sirve así. El archivo.txt tiene un formato

2013-05-31 09:00:15 172.16.18.5 "blabla bla Juan blablabla" campo5 campo6
2013-05-31 09:00:18 172.19.194.101 "blabla bla Antonio blablabla blabla bla" campo5 campo6 campo 7 campo8
2013-05-31 09:00:21 172.16.18.5 "blabla Juan blablabla" campo5
...

Al hacer el split() con cinco campos:

$campo[0] = 2013-05-31
$campo[1] = 09:00:15
$campo[2] = 172.16.18.5
$campo[3] = "blabla
$campo[4] = bla Juan blablabla" campo5 campo6

El $campo[3] es siempre "blabla, con lo cual no encuentra los nombres que tengo que buscar en medio del texto (Juan, Antonio, etc). La idea era buscar un string dentro del texto que va entre comillas y, si lo encuentra, sustituir todo ese texto por un nuevo string.

Supongo que necesito partir el fichero considerando el espacio en blanco como separador de campos, pero pudiendo indicar que las comillas son un delimitador de texto y por tanto no se debe partir en campos el texto que esté entre comillas.

Gracias por tu ayuda.
bvayap
Perlero nuevo
Perlero nuevo
 
Mensajes: 31
Registrado: 2013-05-31 02:42 @154

Publicidad

Re: Leer y reemplazar cadena de texto

Notapor explorer » 2013-06-06 06:18 @304

Buscar algo entrecomillado puede ser muy fácil o muy difícil, si tenemos claro que entre esas comillas no hay otra comillas.

La siguiente versión tiene en cuenta el caso que comentas: el cuarto campo contiene una sección entrecomillada, y es en ella donde tenemos que buscar las correspondencias para hacer los cambios.
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use strict;                                     # estilo estricto
  3. use warnings;                                   # activar advertencias
  4. use diagnostics;                                # diagnósticos de fallos
  5. use autodie;                                    # «Es mejor morir que regresar con deshonor» --proverbio Klingon
  6.  
  7. ### Variables
  8. my %redes;                                      # almacén con los cambios
  9. my %agentes = (
  10.     Juan    => '22333444X',
  11.     Antonio => '11111119H',
  12. );
  13.  
  14.  
  15. ### Lectura del archivo CSV
  16. open my $CSV, '<', 'archivo.csv';
  17.  
  18. while (<$CSV>) {                                # para cada línea, hasta fin de archivo
  19.     chomp;                                      # quitamos caracteres fin de línea
  20.     my($nombre, $red) = split /[;]/;            # partimos la línea, extraemos columnas
  21.  
  22.     $redes{$red} = $nombre;
  23. }
  24.  
  25. close $CSV;
  26.  
  27. ### Procesado
  28. open my $ARCHIVO, '<', 'archivo.txt';           # Lectura del archivo a tratar
  29. open my $SALIDA,  '>', 'archivo.new.txt';       # Escritura del archivo resultado
  30.  
  31. while (my $linea = <$ARCHIVO>) {                # para cada línea del $ARCHIVO
  32.     my(@campos) = split " ", $linea, 4;         # partir en cuatro cachos
  33.     my $IP      = $campos[2];
  34.     my($sujeto) = $campos[3] =~ /"(.+?)"/;      # extraemos la parte entrecomillada
  35.  
  36.     if (exists $redes{$IP}) {                   # si tenemos que cambiarla
  37.         $campos[2] = $redes{$IP};               # la cambiamos
  38.     }
  39.    
  40.     for my $agentN (keys %agentes) {
  41.         if ($sujeto =~ m/$agentN/) {                    # si la cuarta columna contiene $agentN
  42.             $campos[3] =~ s/".+?"/"$agentes{$agentN}"/; # cambiamos la parte entrecomillada
  43.             last;                                       # y salimos del bucle
  44.         }
  45.     }
  46.  
  47.     print $SALIDA join " ", @campos;                    # guardamos el resultado
  48. }
  49.  
  50. close   $ARCHIVO;
  51. close   $SALIDA;
Coloreado en 0.003 segundos, usando GeSHi 1.0.8.4
Este es el resultado:
Sintáxis: [ Descargar ] [ Ocultar ]
Using text Syntax Highlighting
2013-05-31 09:00:15 Sede_Cantabria "22333444X" campo5 campo6
2013-05-31 09:00:18 Sede_Leon "11111119H" campo5 campo6 campo 7 campo8
2013-05-31 09:00:21 Sede_Cantabria "22333444X" campo5
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4
Esto es lo que hacemos: seguimos partiendo la línea en cuatro partes. De la cuarta parte, extraemos lo primero que encontremos que esté entrecomillado y lo guardamos en $sujeto (por llamarlo de alguna manera). Luego, en la comparación con los hash, si alguna clave se encuentra dentro de ese $sujeto, hacemos una operación de sustitución: cambiamos lo que encontremos en el primer par de comillas dobles por el valor correspondiente a la clave coincidente. El resto de $campos[3] no es tocado, tenga las columnas que tenga.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Leer y reemplazar cadena de texto

Notapor bvayap » 2013-06-06 09:38 @443

Hola explorer,

Ahora sí lo hace bien, pero da un warning en cada línea que procesa de archivo.txt:

Use of uninitialized value $sujeto in pattern match (m//) at line 41

Lo que no entiendo, porque $sujeto está definido en la 34 y dentro del bucle while, ¿no?

Por otra parte, estoy intentando que $IP no tenga que ser exactamente igual al valor en %redes, sino contenerlo, ya que así me evito procesar el archivo.txt para eliminar la parte de host, como estaba haciendo hasta ahora. He probado cambiando las líneas 36-38 por:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.                 for my $redCentro (values %redes) {
  2.                 if ($IP =~ m/$redCentro/) {
  3.                         $campos[2] = $redes{$IP};
  4.                 }
  5.  
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
pero de esta forma no me hace el cambio.
bvayap
Perlero nuevo
Perlero nuevo
 
Mensajes: 31
Registrado: 2013-05-31 02:42 @154

Re: Leer y reemplazar cadena de texto

Notapor explorer » 2013-06-06 11:43 @530

Si sale el error de que $sujeto está indefinido, es que no existen parejas de comillas en $campos[3].

En cuanto a solo cambiar las IP, ya lo hicimos al principio de este hilo, ¿no?
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Leer y reemplazar cadena de texto

Notapor bvayap » 2013-06-06 13:32 @606

Lo del Warning lo compruebo mañana, porque es cierto que hay algunas líneas en los que el campo está vacío, así que comprobaré si ocurre en todas o sólo en las que está vacío.

En cuanto a lo de las IP, estaba resuelto pero comprobando que la IP del campo 2 fuese exactamente igual a la red de la sede ( tenía un if (exists)). Pero eso me obliga a procesar antes ese fichero.txt para eliminar la parte de host de la IP. Por eso lo que pretendo ahora es que se compruebe si $IP contiene la red del centro, para no tener que procesar antes con un vbscript, que tarda lo suyo ;-)

Bueno, mañana sigo probando y os cuento. Gracias por todo.
bvayap
Perlero nuevo
Perlero nuevo
 
Mensajes: 31
Registrado: 2013-05-31 02:42 @154

Re: Leer y reemplazar cadena de texto

Notapor explorer » 2013-06-06 19:34 @857

Con mi solución de expresión regular (en la primera página de este hilo), puedes hacer la sustitución de las IP de forma rápida. Ya lo probaste, ¿no?

De todas maneras, no hay mucha ganancia de velocidad el hacerlo por separado...

El comparar si el segundo campo es alguna de las IP que tenemos en el hash es algo normal y lógico, porque se supone que solo habrá IP en ese campo. Otra cosa es que digas que pueden aparecer en otros campos.

Entonces, puedes hacer esto: lees todo el $archivo de golpe, haces las transformaciones con la expresión regular y el operador de sustitución (s///) y luego partes $archivo en líneas (con split) recorriendo cada línea y haciendo el resto de cambios y guardando el resultado. Sigue siendo un solo programa.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Leer y reemplazar cadena de texto

Notapor bvayap » 2013-06-07 06:43 @322

Hola explorer,

Ya he conseguido que se compruebe únicamente la parte de red de la sede en archivo.txt (y no que coincidan exactamente, como lo tenía hasta ahora). Ahí va el código (creo que definitivo, por fin :-) )

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/perl
  2. use strict;                            # estilo estricto
  3. use warnings;                          # activar advertencias
  4. use diagnostics;                       # diagnósticos de fallos
  5. use autodie;                           # «Es mejor morir que regresar con deshonor» --proverbio Klingon
  6.  
  7. # Variables
  8. my %redes;                             # Definimos el hash %redes
  9. my %agentes = (
  10.     Juan    => '22333444X',
  11.     Antonio => '11111119H',
  12. );
  13.  
  14. ### Lectura del archivo CSV
  15. open my $CSV, '<', 'archivo.csv';      # Leemos el fichero con las Sedes
  16.  
  17. while (<$CSV>) {                       # para cada línea, hasta fin de archivo
  18.     chomp;                             # quitamos caracteres fin de línea
  19.     my ( $red, $nombre ) = split /[;]/;    # partimos la línea, extraemos columnas y las metemos en el hash %redes
  20.     $redes{$red} = $nombre;            # Metemos $nombre como value del hash %redes
  21. }
  22. close $CSV;                            # Cerramos el fichero
  23.  
  24. ### Procesado
  25. open my $ARCHIVO, '<', 'archivo.txt';  # Lectura del archivo a tratar
  26. open my $SALIDA,  '>', 'archivo.new.txt';  # Escritura del archivo resultado
  27.  
  28. while ( my $linea = <$ARCHIVO> ) {     # para cada línea del $ARCHIVO
  29.     my (@campos) = split " ", $linea, 4;    # partir en cuatro cachos
  30.     my $IP = $campos[2];               # Metemos la IP del cliente en $IP
  31.     my ($sujeto) = $campos[3] =~ /"(.+?)"/; # extraemos la parte entrecomillada del cuarto campo
  32.  
  33.     for my $redC ( keys %redes ) {     # Recorremos la red de las sede
  34.         if ( $IP =~ m/$redC/ ) {       # vemos si la IP del cliente contiene la red de la sede
  35.             $campos[2] = $redes{$redC};     # y si la contiene la cambiamos
  36.             last;                      # y salimos del bucle
  37.         }
  38.     }
  39.  
  40.     for my $agentN ( keys %agentes ) { # Recorremos el nombre del agente
  41.         if ( $sujeto =~ m/$agentN/ ) { # si la cuarta columna contiene $agentN
  42.             $campos[3] =~ s/".+?"/"$agentes{$agentN}"/;    # cambiamos la parte entrecomillada por el DNI
  43.             last;                      # y salimos del bucle
  44.         }
  45.     }
  46.  
  47.     print $SALIDA join " ", @campos;   # guardamos el resultado
  48. }
  49.  
  50. close $ARCHIVO;
  51. close $SALIDA;
  52.  
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4


Como comentabas, el warning sale porque efectivamente hay líneas en las que el campo[3] es un guión ("-") y no tiene nada entrecomillado, pero no he conseguido evitar este warning.

Muchas gracias por tu ayuda y tiempo.
Última edición por explorer el 2013-06-07 12:07 @546, editado 1 vez en total
Razón: Formateado de código con Perltidy
bvayap
Perlero nuevo
Perlero nuevo
 
Mensajes: 31
Registrado: 2013-05-31 02:42 @154

Re: Leer y reemplazar cadena de texto

Notapor danimera » 2013-06-07 08:52 @411

Puedes tratar de validar antes, para evitar el warning, pero si no también puedes usar el no uso de warning :P

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. no warnings;
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
100% Telch - Perl Web Programming
Cali PerlMongers: http://cali.pm.org
Avatar de Usuario
danimera
Perlero frecuente
Perlero frecuente
 
Mensajes: 871
Registrado: 2005-06-23 19:02 @834
Ubicación: Colombia

Re: Leer y reemplazar cadena de texto

Notapor explorer » 2013-06-07 13:47 @616

La solución más obvia es la que te comenta danimera, claro. También puedes hacerlo quitando o comentando la línea 'use warnings'.

También puedes hacer esa comprobación antes, con lo que ganas en velocidad, ya que no se hace ninguna búsqueda:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1.     if (defined $sujeto) {
  2.         for my $agentN ( keys %agentes ) { # Recorremos el nombre del agente
  3.             if ( $sujeto =~ m/$agentN/ ) { # si la cuarta columna contiene $agentN
  4.                 $campos[3] =~ s/".+?"/"$agentes{$agentN}"/;    # cambiamos la parte entrecomillada por el DNI
  5.                 last;                      # y salimos del bucle
  6.             }
  7.         }
  8.     }
  9.  
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4


Lo que no me gusta es lo del cambio de la IP. Tal cual lo muestras es que estás indicando que $campos[2] contiene algo más de texto que una simple IP. ¿Es así? Porque no es lo que nos has enseñado en los ejemplos...

Además hay un problema: si estamos buscando por la IP 127.0.0.1, coincidirá erróneamente con 127.0.0.11, o incluso 127.0.0.111. Debes cambiar el patrón de la expresión regular para agregarle las marcas '\b', para indicar que después (y antes) de la IP nos interesa que no haya nada más que no sea un límite de palabra:

$IP =~ m/\b$redC\b/

Lo mismo se puede decir de la búsqueda de $agentN: si buscamos por "Juan", también coincidirá con "Juan Antonio".

Otro tema también es el de los '.' dentro de las IP: son comodines para la expresión regular. No es problemático, pero en algunos cosas podría dar sorpresas. Por ejemplo, si buscamos por 127.0.0.1, coincidirá con el número 127802061.

Hay una forma de aumentar la velocidad: si supiéramos que no tenemos los problemas comentados antes (eso depende del verdadero formato de los campos), se pueden sustituir las búsquedas -por medio de expresiones regulares- a búsquedas realizadas con la función index(), que dará una respuesta mucho más rápida.
JF^D Perl programming & Raku programming. Grupo en Telegram: https://t.me/Perl_ES
Avatar de Usuario
explorer
Administrador
Administrador
 
Mensajes: 14480
Registrado: 2005-07-24 18:12 @800
Ubicación: Valladolid, España

Re: Leer y reemplazar cadena de texto

Notapor bvayap » 2013-06-10 05:26 @268

Hola,

Gracias a ambos por las aportaciones.

Tienes razón, explorer: estaba trabajando con ficheros de ejemplo y, a raíz de tu comentario, veo que la he liado un poco, ya que en archivo.csv que puse aparecía la IP del cliente y no la red de la sede, como es el caso. Perdón por el error. A ver si aclaro lo de la IP. Los ficheros tienen los formatos siguiente:
Sintáxis: (archivo.csv) [ Descargar ] [ Ocultar ]
Using text Syntax Highlighting
172.16.18.;Sede_Cantabria
172.19.194.;Sede_Leon
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4

Sintáxis: (archivo.txt) [ Descargar ] [ Ocultar ]
Using text Syntax Highlighting
2013-05-31 09:00:15 172.16.18.5 "blabla bla Juan blablabla" campo5 campo6
2013-05-31 09:00:18 172.19.194.101 "blabla bla Antonio blablabla blabla bla" campo5 campo6 campo 7 campo8
2013-05-31 09:00:21 172.16.18.5 "blabla Juan blablabla" campo5
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4

Es decir: los tres primeros octetos del campo IP Cliente del archivo.txt coinciden con el campo IP Sede en archivo.csv (campo que además termina con "."). De ahí el interés en comprobar que el campo IP Cliente CONTENGA el campo IP Sede, y no que sean COINCIDENTES como hacía al principio con el if exists.

Lo que hacía era procesar antes archivo.txt con un VBScript que eliminaba el último octeto del campo IP Cliente, con lo que en ese caso ambos campos ya eran coincidentes, pero claro, al trabajar con los archivos reales (sobre 1 GB de texto) el rendimiento de este VBScript no era bueno.

Ahora, al cambiar
if (exists $redes{$IP}) {
por
if ($IP =~ m/\b$redC\b/) {

evidentemente el rendimiento ha empeorado, cosa que veo lógica ya que a nivel de cálculo, no es lo mismo buscar una cadena que contenga otra que comparar si dos cadenas coinciden.

Espero haber aclarado las cosas un poco y no haberlas liado más. Os pido disculpas por el error.

Voy a echar un vistazo a la función index() que comenta explorer a ver si me aclaro y consigo mejorar el rendimiento. Gracias por vuestra ayuda.
bvayap
Perlero nuevo
Perlero nuevo
 
Mensajes: 31
Registrado: 2013-05-31 02:42 @154

AnteriorSiguiente

Volver a Básico

¿Quién está conectado?

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