Moviendonos entre archivos con python

Iniciado por Once, Enero 22, 2015, 04:14:47 AM

Tema anterior - Siguiente tema

0 Miembros y 1 Visitante están viendo este tema.

Enero 22, 2015, 04:14:47 AM Ultima modificación: Enero 23, 2015, 04:04:52 PM por Once
Fuente: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta


A veces, cuando tratamos con archivos necesitamos algo más que sólo leer todo el archivo. Necesitamos poder movernos entre el archivo y leer partes que bien podrían resultar aleatorias. En esta entrada veremos esos métodos que nos brinda Python para conseguir ¡movernos entre los archivos!

Abriendo el archivo

Python nos da dos opciones para abrir un archivo:

Código: python
archivo = open(ruta_archivo, modo_apertura)  # Abrimos el archivo
archivo.read()
archivo.close()  # Cerramos el archivo



Código: python
with open(ruta_archivo, modo_apertura) as archivo:  # Abrimos el archivo
    print archivo.read()



En la primer forma nos tenemos que preocupar por cerrar el archivo, en la segunda forma, cuando salimos del bloque with Python se encarga de cerrar el archivo por nosotros. Ambas formas retornan un objeto tipo file.

Modos de apertura


Modo apertura    Significado
r    Abre el archivo en modo sólo lectura.
w    Abre el archivo en modo sólo escritura. Si ya existe el archivo éste se sobrescribe; de lo contrario, se crea el archivo.
a    Abre el archivo en modo escritura. Si el archivo ya existe se escribe la información al final.
r+    Abre el archivo en los modos lectura y escritura.
rb    Abre el archivo en modo sólo lectura binaria.
rw    Abre el archivo en modo sólo escritura binaria.
r+b    Abre el archivo en los dos modos lectura y escritura binaria.

En Windows se distingue entre archivos de texto y archivos binarios. Así que debemos tener cuidado con el modo en el que abrimos los archivos.

   
Código: python
with open("data.txt", "w") as archivo:  # Abrimos el archivo en modo escritura
    print archivo.write("Hola mundo")  # Escribimos en el archivo


En el script anterior abrimos el archivo data.txt y escribimos en el "Hola mundo"

Métodos del objeto file

Leyendo el archivo

Código: python

archivo.read([tamaño])


El argumento tamaño es opcional y determina la cantidad de bytes que van a ser leídos. Si no se especifica, se leerá todo el archivo.
   
Código: python
archivo.readline()


El método readline() nos permite leer sólo una línea del archivo.

   
Código: python
arhivo.readlines()


El método readlines() lee todo el archivo y regresa una lista donde cada elemento es una línea del archivo.

Cada que leemos en el archivo también nos movemos en él, así, la próxima vez que leamos lo haremos un byte a la derecha desde la última posición.
Cuando llegamos al final del archivo, la lectura nos devolverá una cadena vacía.

Leyendo la versión de un PDF

La cabecera de los archivos PDF tienen el formato: %PDF-1.N(EOL)
Donde N es un número que puede ir desde uno hasta siete y representa la especificación y (EOL) representa un final del línea que puede ser un retorno de carro, una línea nueva o ambas.


Así pues,  tenemos dos opciones para leer la versión de un archivo PDF.
   
Código: python
with open("pdf.pdf", "r") as archivo:  # Abrimos el archivo
    print archivo.readline()  # Leemos la primer línea


Leemos la primer línea completa del archivo.
Citar%PDF-1.4\n

   
Código: python
with open("pdf.pdf", "r") as archivo:  # Abrimos el archivo
    print archivo.read(8)  # Leemos los primeros ocho bytes del archivo


Sólo leemos los primeros ocho bytes del archivo.
Citar%PDF-1.4

Otros métodos del objeto file

Conocer la posición en el archivo

Como ya se explicó antes, cada que leemos el archivo, también nos movemos en el, con el método tell() Python nos permite conocer la posición (el byte) donde estamos en el archivo.
   
Código: python
with open("pdf.pdf", "r") as archivo:  # Abrimos el archivo
    print "Posición inicial:", archivo.tell()
    print "Cabecera:", archivo.read(8)
    print "Posición final:", archivo.tell()


CitarPosición inicial: 0
Cabecera: %PDF-1.4
Posición final: 8

Moviéndonos entre el archivo
   
Código: python
archivo.seek(bytes, [desde_donde])


El argumento bytes indica la cantidad de bytes que nos vamos a mover dentro del archivo.

El argumento desde_donde es opcional, puede tomar tres valores 0, 1 y 2 e indica desde donde nos vamos a mover. 0 (valor por defecto) indica que nos vamos a mover desde el principio del archivo. 1 indica que nos vamos a mover desde donde se encuentra actualmente el puntero. 2 indica que nos vamos a mover desde el final del archivo.

Si el argumento desde_donde es especificado y es distinto que 0, entonces el argumento bytes puede tomar valores negativos y positivos. Un valor positivo indica que nos vamos a mover hacía la derecha y un valor negativo indica que nos vamos a mover hacía la izquierda.

Leyendo un archivo desde atrás
A veces necesitamos leer los archivos desde atrás ya sea porque las especificaciones así lo requieren (como en el formato PDF) o porque lo que nos interesa está ahí ej. los típicos casos de esteganografía por EOF (End Of File) donde la información que queremos ocultar la guardamos al final de una imagen. Si no pudiéramos movernos entre el archivo, tendríamos que leer todo el archivo y luego sí leer la parte que necesitamos.

Para este ejemplo vamos a usar la imagen principal de la entrada que guardaremos como "moviendonos.png". He guardado un pequeño mensaje ahí que ocupa los últimos 26 bytes.

   
Código: python
with open("moviendonos.png", "r") as archivo:  # Abrimos el archivo
    archivo.seek(1, 2)  # Nos movemos al final del archivo
    buffer = ""
    for _ in xrange(26):  # Recorremos los 26 bytes
        archivo.seek(-2, 1)  #  Nos movemos dos bytes a la izquierda
        buffer += archivo.read(1)  # Leemos un byte

print buffer


En el ejemplo anterior estamos abriendo un archivo binario en modo texto (lo ideal es hacerlo en modo binario) "rb" Gracias a Azav por la observación

Recordemos que cuando leemos el archivo sin importar el método que usemos, Python comenzará a leer desde el byte a la derecha de donde se encuentra el puntero. Por eso en la quinta línea nos movemos dos bytes a la izquierda y cuando leemos en la sexta línea nos movemos un byte a la derecha.

El resultado es:


CitarNo tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Por lo general se usa un "separador" para delimitar donde acaba el archivo original y comienza nuestro mensaje; así que si no conocemos la cantidad de bytes que tiene el mensaje escondido, podemos seguir leyendo hasta que encontremos el separador. Teniendo en cuenta que como estamos leyendo desde atrás, el mensaje también lo estaremos leyendo desde atrás.

Si abrimos la imagen con un editor hexadecimal podemos ver que el mensaje está escrito al revés. Por eso cuando lo leemos es legible.


Con esto terminamos la entrada de hoy, cualquier comentario es bienvenido.

Fuente: No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Saludos!
Once







No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Muy buen tutorial Once, la verdad es que estoy empezando con python y me viene muy bien!

Gracias ^^



Gracias brother, espero que te sirva.

Saludos!







No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Enero 22, 2015, 05:12:15 PM #3 Ultima modificación: Enero 22, 2015, 05:17:56 PM por Azav
Vaya coincidencia que publiques esto porque justo la semana pasada estuve explorando esto en python. Me parece bueno el artículo, bien explicado, sencillo y fácil de entender.

Aprovecho de hacer algunos comentarios. Notar que a diferencia de Once yo uso Python 3.x y él usa 2.x y eso significa que podrían haber varias diferencias.

Primero, en los modos de apertura, cuando uno no escribe la letra b, que significa abrir como binario, automáticamente el archivo se abre como texto (sería lo mismo que escribir una t) y a mi juicio uno no debería abrir nunca un archivo binario en modo texto. Hago este comentario porque en el apartado Leyendo un archivo desde atrás tu abres una imagen .png como texto (ya que no especificaste la letra b) y existen muchos bytes que no pueden leerse como strings, lo que traería como resultado una excepción del tipo UnicodeError. Para ser más claro, si abres el archivo moviendonos.png con un editor hexadecimal y agregas un byte 81 al final del archivo, entonces va a surgir un UnicodeError al intentar leer los últimos bytes en modo texto.

Esto se debe a que existen ciertos bytes (81, 8D, 8F, 90, y 9D) que no tienen un carácter asociado y arrojan un UnicodeDecodeError al intentar transformarlos a string. También hay otro grupo de bytes que si bien tienen caracteres asociados, esos caracteres no son imprimibles (ocurre con casi todos entre el byte 80 y el FF y entre el 00 y el 20) y al intentar imprimirlos van a lanzar un UnicodeEncodeError aunque estos últimos van a depender dónde se quieran imprimir.

La solución es abrir el archivo como binario, leer todos sus bytes y luego intentar pasarlos a string usando el método decode(), publico una parte del código que hice en mi investigación la semana pasada, aplicado al caso:

Código: python
targetfile = open('moviendonos.png', 'rb')
targetfile_data = targetfile.read() #Data como bytes
targetfile.close()
string = targetfile_data.decode('cp1252', 'replace')[-27:] #Data como string.
"""
Me llevo mucho tiempo descubrir que encriptación debia usar para decodificar los bytes. Después de muchas pruebas descubri que era 'cp1252'. Incluso tuve que crear otro programa para descubrirla, en la documentación de python no la encontré.
El modo 'replace' reemplaza los bytes que no tienen caracteres asociados sin embargo quedan los bytes que tienen caracteres asociados no imprimibles.
"""
#Reemplazamos los caracteres no imprimibles por espacios en blanco.
pos_no_imprimibles = []
while True:
try:
print(string)
break
except UnicodeEncodeError as EncError:
PosErr = EncError.args[2] #El error nos dice la posicion del caracter no imprimible
"""
La posicion que nos entrega el UnicodeEncodeError tiene el problema de que en un string considera el '\n' como dos caracteres.
De modo que si el caracter no imprimible es \CNI y tenemos el string "\n\CNI", segun UnicodeEncodeError la posicion de \CNI es 2, mientras que la posicion real es 1.
Este es un error que demoré mucho en descubrir y para repararlo es necesario este codigo.
"""
contador = 0
result = -1
while True:
result = string[:PosErr - contador].find("\n", result + 1)
if result != -1:
contador += 1
else:
break
PosErr = PosErr - contador
"""Fin del codigo reparador"""
string = string[:PosErr] + " " + string[PosErr + 1:] #Reemplazamos el caracter no imprimible por un espacio


Tal vez me excedí en longitud del comentario pero quería aprovechar de compartir mis investigaciones. Fuera de esas sutilizas esta todo perfecto. ¡Gracias Once!

Excelente aporte Once! Y muchas gracias Azav por complementarlo con tu información!

Así es como crecemos entre todos.

Un saludo y gracias por compartir!
WhiZ


@Azav

Tienes toda la razón brother, como fui yo quien agregó la información al final del archivo no contemplé la posibilidad de que hubieran bytes no imprimibles. Ahí hago la aclaración. Muchas gracias por la observación!

En cuando al código que publicaste, no estoy muy familiarizado con Python 3.* pero la idea de la entrada era justo mostrar que se puede leer partes del archivo sin la necesidad de tener que leer todo el archivo. Piensa que tienes un archivo algo grande y lo único que necesitas es sacarle los metadatos, leer todo el archivo sería un gasto innecesario de memoria y tiempo. Mira que tienes que leer todo el archivo para usar sólo los últimos 27 bytes.

Por cierto, me parece que te complicaste mucho para sacar los caracteres imprimibles. Así lo haría yo, usando una lista blanca:

Código: python
# -*- coding: utf-8 -*-

import string

permitidos = string.ascii_letters + string.digits  # Lista blanca

with open("moviendonos.png", "rb") as archivo:
    archivo.seek(1, 2)
    buffer = ""
    for _ in xrange(27):
        archivo.seek(-2, 1)
        byte = archivo.read(1)
        if byte in permitidos:  # Verificamos si el byte leído está en la lista
            buffer += byte

print buffer


Así todos los caracteres deberían ser imprimibles. (Aunque no estoy muy seguro si es a lo que ter refieres)

Saludos y gracias de nuevo!







No tienes permitido ver los links. Registrarse o Entrar a mi cuenta

Enero 23, 2015, 12:30:11 AM #6 Ultima modificación: Enero 23, 2015, 12:36:12 AM por Azav
¡Vaya! No tenía idea de la libreria string. De haberla conocido me hubiera ahorrado bastante tiempo, aunque la verdad nunca se me hubiera ocurrido hacer una lista blanca. Definitivamente era muchísimo más simple así.

Algunos comentarios del código. Los caracteres imprimibles no son sólo letras y dígitos, también son los símbolos, los saltos de linea, etc. Para eso tu librería string cuenta con un atributo más específico aún: string.printable. Lo otro que veo es que en la linea 13 tu buscas un byte en una cadena de strings (osea nunca vas a encontrar un byte en la lista). Usando los métodos .encode() para strings y .decode() para bytes se puede solucionar el asunto. El código modificado y funcional para mí sería:

Código: python
import string

permitidos = string.printable.encode('cp1252')  # Lista blanca DE BYTES

with open("moviendonos.png", "rb") as archivo:
    archivo.seek(1, 2)
    buffer = ""
    for _ in range(27):
        archivo.seek(-2, 1)
        byte = archivo.read(1)
        if byte in permitidos:  # Verificamos si el byte leído está en la lista blanca de bytes.
            buffer += byte.decode('cp1252') #De estarlo, lo transformamos nuevamente a string y lo unimos a buffer.

print(buffer)


Nunca se me hubiera ocurrido hacerlo así, pero claramente era una excelente idea, nos evitamos usar try-except y manejar excepciones que puede ser muy tedioso. ¡Gracias Once!