Página 1 de 2

Modificar líneas en fichero

NotaPublicado: 2017-12-03 12:21 @556
por boligan
Hola.

Bueno, resulta que tengo varios archivos xml con datos los cuales necesito actualizar cada vez que tengo nuevos datos.

Lo que deseo saber es si es posible insertar los nuevos datos antes de cerrar el xml:

<label>
.datos viejos
.datos viejos
</label>

O sea, insertar los nuevos datos antes de </label>:

<label>
.datos viejos
.datos viejos
.datos nuevos
.datos nuevos
</label>

Esto deseo hacerlo hacer sin tener que utilizar los métodos de copiar para otro archivo o guardar en arreglo, o sea si es posible hacerlo en el mismo archivo. Leí que desde consola se puede hacer así:

perl -p -i -e '$_="" if /es la línea que busco/' fichero.txt

pero resulta que yo lo que tengo es un script y no sé (ni sé cómo) si esto lo puedo incluir en mi script.

Por favor, alguna idea...

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-03 13:39 @610
por explorer
Si eres capaz de crear una expresión regular que identifique de forma única la línea del archivo que quieres modificar, entonces sí que es una buena solución usar el modo de reemplazo "in-situ".

Si resulta que '</label>' es la última línea que debes modificar (y es la única del archivo así), entonces ese debe ser tu patrón:
Sintáxis: [ Descargar ] [ Ocultar ]
Using bash Syntax Highlighting
  1. perl -p -i -e 's{^</label>$}{.datos nuevos\n.datos nuevos\n</label>}' archivo.txt
Coloreado en 0.006 segundos, usando GeSHi 1.0.8.4


Lo que está haciendo el programa es abrir archivo.txt y reeditarlo (-i). Para ello, lo lee línea a línea, y a cada una, le aplica la expresión regular de sustitución. Finalmente, imprime la línea resultado (-p) en el propio archivo. Bueno, en realidad escribe el resultado en un archivo temporal y luego le cambia el nombre para sobreescribir el original.

Si la línea que está procesando coincide con el patrón de búsqueda, sustituye lo encontrado por el texto que le sigue. Mira:
Sintáxis: [ Descargar ] [ Ocultar ]
Using text Syntax Highlighting
explorer@Arcanus:~/Documentos/Desarrollo > cat kk.txt
<label>
.datos viejos
.datos viejos
</label>
explorer@Arcanus:~/Documentos/Desarrollo > perl -p -i -e 's{^</label>$}{.datos nuevos\n.datos nuevos\n</label>}'  kk.txt
explorer@Arcanus:~/Documentos/Desarrollo > cat kk.txt
<label>
.datos viejos
.datos viejos
.datos nuevos
.datos nuevos
</label>
explorer@Arcanus:~/Documentos/Desarrollo >
Coloreado en 0.000 segundos, usando GeSHi 1.0.8.4


La cuestión, ahora, es cómo realizar esto dentro de un programa.

Primero, hay que activar la variable especial $^I, opcionalmente con el valor de la extensión que se añadirá al archivo original.
Segundo, agregar el archivo original a @ARGV.
Tercero, hacer un bucle por las filas leyéndolas con <>, y usar la expresión regular e imprimir el resultado.

Algo así:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. #!/usr/bin/env perl
  2.  
  3. local $^I   = '.bak';                   # emula  -i.bak
  4. local @ARGV = glob("kk.txt");           # initializa lista de archivos
  5.  
  6. while (<>) {
  7.     s{^</label>$}{.datos nuevos\n.datos nuevos\n</label>};
  8.     print;
  9. }
Coloreado en 0.002 segundos, usando GeSHi 1.0.8.4

Este programa genera la misma salida que el uno-línea anterior.

Más información, en la receta 7.9 del libro Perl CookBook..

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-03 19:11 @841
por boligan
Hola. Bueno, traté de probar con la solución que me diste, pero realmente, como no entiendo bien qué es lo que hacen esas variables especiales, no logré implementar esa solución, se me ocurrió hacerlo de esta forma:

...; # esta es una función de mi programa...

sub principio_y_final {
foreach ( glob("$carpeta/*.xml") ) {
@file_full = (); # inicio arreglo vacío
$file_entrada = $_; # ficheros de entrada
$file_salida = "$_.old"; # ficheros de salida
rename $file_entrada, $file_salida;
open( IN, "<$file_salida" ) || die "ERROR: No abre fichero de entrada $file_salida\n";
open( OUT, ">$file_entrada" ) || die "ERROR: No abre fichero de salida $!\n";
while ( $file = <IN> ) { # lee una línea del archivo
next if m/<\/Pinar\>/;
if ( $file !~ m/<\/Pinar\>/ ) {
push @file_full, $file;
}
}
print OUT @file_full;
print OUT "</Pinar>\n";
close IN;
close OUT;
unlink $file_salida; # borro los ficheros .old
}
return;
}

Me funciona bien excepto con el último archivo. Tengo tres y al último ni lo abre (me da error), ni tampoco le agrega la marca </Pinar> (por supuesto), entonces si no es molestia tírale un vistazo y dime qué hago mal...

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-03 20:47 @907
por explorer
Pues es una pena, porque te ahorrarías muchas de esas líneas. El ejemplo que aparece en el enlace que te he puesto sirve perfectamente para el caso de editar múltiples archivos. En fin...

Hay un error grave dentro del bucle while().

Tienes puesto

next if m/<\/Pinar\>/;

Ahí estás comparando el valor de $_ con el patrón m//... pero resulta que $_ NO contiene la línea que estás leyenda de los archivos. La línea está dentro de la variable $file.

El caso es que esa línea, en realidad, sobra, ya que está contemplado el caso de saltarse la marca '</Pinar>' en el if() siguiente.

No sabemos qué error te da al abrir el último archivo. Para saberlo, mete $! en el die de la apertura en lectura de archivos, igual a como lo tienes en el die de apertura en escritura. Eso sacará el mensaje de error.

Yo no veo más fallos.

La reescribiría así (no probado):
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
  1. sub principio_y_final {
  2.     for my $archivo_entrada ( glob("$carpeta/*.xml") ) {
  3.  
  4.         my $archivo_nuevo =  $archivo_entrada;
  5.         my $archivo_viejo = "$archivo_entrada.old";
  6.         rename $archivo_nuevo, $archivo_viejo;          # nuevo => viejo
  7.  
  8.         open my  $IN, '<', $archivo_viejo       or die "ERROR: No abre archivo de entrada $archivo_viejo: $!\n";
  9.         open my $OUT, '>', $archivo_nuevo       or die "ERROR: No abre archivo de salida  $archivo_nuevo: $!\n";
  10.  
  11.         while ( my $linea = <$IN> ) {                   # lee una línea del archivo
  12.             if ( $linea =~ m{</Pinar>} ) {              # si es la marca que buscamos
  13.                 print $OUT "nueva información\n";      # insertamos la nueva información
  14.             }
  15.             print $OUT $linea;                          # copiamos la línea leída al archivo de salida
  16.         }
  17.  
  18.         close $IN;
  19.         close $OUT;
  20.  
  21.         unlink $archivo_viejo;                          # borro los ficheros .old
  22.     }
  23.  
  24.     return;
  25. }
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-03 22:14 @968
por boligan
Hola.

Bueno, estoy probando tu código y me sale el mismo error. Seguí tu sugerencia de poner $! y este es el error:

ERROR: No abre archivo de entrada L:/ruta/2020-01.xml.old: No such file or directory

Al parecer no me está creando el archivo .old. Sin embargo, le agregué algunos print a tu código para guiarme y sale esto:

for my $archivo_entrada ( glob("$carpeta/*.xml") ) {
my $archivo_nuevo = $archivo_entrada;
my $archivo_viejo = "$archivo_entrada.old";
print "encabeza-$archivo_nuevo\n";
print "encabeza-$archivo_viejo\n";
print rename $archivo_nuevo, $archivo_viejo;
# nuevo => viejo
open my $IN, '<', $archivo_viejo or die "ERROR: No abre archivo de entrada $archivo_viejo: $!\n";
open my $OUT, '>', $archivo_nuevo or die "ERROR: No abre archivo de salida $archivo_nuevo: $!\n";
while ( my $linea = <$IN> ) { # lee una línea del archivo
if ( $linea =~ m{</Pinar>} ) { # si es la marca que buscamos
print $OUT "nueva información\n"; # insertamos la nueva información
}
print $OUT $linea; # copiamos la línea leída al archivo de salida
}
close $IN;
close $OUT;
unlink $archivo_viejo; # borro los ficheros .old
}
return;
__________________________________________
encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/1987-05.xml
encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/1987-05.xml.old
1encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/2013-01.xml
encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/2013-01.xml.old
1encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/2020-01.xml
encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/2020-01.xml.old
ERROR: No abre archivo de entrada L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/2020-01.xml.old: No such file or directory
0Presione una tecla para continuar . . .

________________________________________
Como ves no me hace el último rename o al menos eso creo, por el cero que pone. Una duda que tengo es por qué me pone el resultado del rename después de tratar de abrir el fichero, en realidad yo lo mando a renombrar antes, pero en el último fichero trata de renombrar después de abrir... no entiendo...

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-04 09:57 @456
por explorer
Sí, la presencia del '0' en el rename() es que quiere decir que se ha producido ahí un error.

Para saber qué error es, debes sacar la variable $!.

Sería algo así:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
if (not rename $archivo_nuevo, $archivo_viejo) {
        die "ERROR al renombrar: $!\n";
}
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4
o así:
Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
rename $archivo_nuevo, $archivo_viejo  or  die "ERROR al renombrar: $!\n";
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

El que salga el '0' después no es importante: seguro que tiene que ver con el búfer de salida (los programas almacenan la salida hasta que se llena el búfer o se cierra el canal de salida). Si no quieres trabajar con búfer de salida, escribe esto al principio del programa:

$|=1;

pero no es recomendable si tu programa genera mucha salida hacia el exterior.

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-04 17:08 @755
por boligan
Hola. Bueno, el error es de permisos:

encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/1933-05.xml
encabeza-L:/TRABAJO-CMP/Boligan/Proyectos/Programacion/Decodificador_OBS_Perl/Proyecto-OBS-DB/1933-05.xml.old

ERROR al renombrar: Permission denied

y si tienes alguna solución por favor te lo agradecería...

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-04 18:50 @826
por explorer
Pues es muy raro... se supone que el programa lo ejecutas tú, y por lo tanto tienes que tener permiso para escribir, modificar y borrar los archivos de esa ruta. Por favor, comprueba que es así. Perl no puede modificar archivos si no pertenecen al usuario/grupo de usuario y tienen los permisos de escritura correspondientes.

Sí que se pueden cambiar los permisos, con chmod, pero también tienes la limitación de poder hacerlo sobre tus archivos y solo sobre los archivos de los que tengas permiso de modificación.

Si te sale ese error... puede ser que el proceso que originalmente dejó allí los archivos, lo hiciese sin darles permiso de escritura suficientes para que tu programa luego los modificase.

Como mínimo... debes tener permiso de escritura y ejecución en la carpeta Proyecto-OBS-DB/ para poder hacer renombrados de archivos.

Si el error te lo da en uno solo de los archivos, el problema no está en la carpeta, sino en el archivo, que no tiene los permisos adecuados... o... algo más sutil: el archivo ha desaparecido cuando Perl va a renombrarle.

Hay una forma de saberlo: con una consulta:

Sintáxis: [ Descargar ] [ Ocultar ]
Using perl Syntax Highlighting
if (! -e $archivo_entrada) {
        die "ERROR: el archivo $archivo_entrada no existe";
}
Coloreado en 0.001 segundos, usando GeSHi 1.0.8.4

Esto me pasó una vez: los archivos aparecían y desaparecían tan rápidos que no me daba tiempo a procesarles. Con esa consulta me aseguraba que solo procesaba los que realmente estaban en el directorio.

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-04 19:13 @842
por boligan
Bueno, estoy en función de los permisos. Tengo control total de la carpeta donde están los ficheros, me sigue dando error. Después cambié para otra partición del HDD y tomé control total de la carpeta, me sigue dando error, ya me tiene loco esto. Seguiré probando.

Gracias.

Re: Modificar líneas en fichero

NotaPublicado: 2017-12-05 02:16 @136
por boligan
Hola. Bueno, ya tengo idea de dónde sale el problema. Resulta que yo estoy leyendo cada línea de un fichero csv y a cada línea que tiene un dato en común (año) le creo un fichero año.xml. Bueno, lo que sucede es que el fichero año.xml que corresponde a la última línea del csv NUNCA me deja ni renombrarlo ni borrarlo.

He tratado varias cosas pero nada me resulta, siempre es el último fichero... Ideas... Gracias...