¿Cómo veo un archivo para ver los cambios?


323

Tengo un archivo de registro que está siendo escrito por otro proceso que quiero ver si hay cambios. Cada vez que se produce un cambio, me gustaría leer los nuevos datos para procesarlos.

¿Cuál es la mejor manera de hacer esto? Esperaba que hubiera algún tipo de gancho de la biblioteca PyWin32. He encontrado la win32file.FindNextChangeNotificationfunción pero no tengo idea de cómo pedirle que vea un archivo específico.

Si alguien ha hecho algo como esto, estaría muy agradecido de saber cómo ...

[Editar] Debería haber mencionado que estaba buscando una solución que no requiere sondeo.

[Editar] Maldiciones! Parece que esto no funciona en una unidad de red asignada. Supongo que Windows no "escucha" ninguna actualización del archivo como lo hace en un disco local.


1
en Linux uno podría usar llamadas de stracemonitoreo writepara esto
test30

La respuesta de @ simao usa python-watchdog. Python-Watchdog tiene una excelente documentación -> aquí hay un enlace a la documentación ["QuickStart"] que proporciona un ejemplo de código mínimo que vigila el directorio de trabajo actual.
Trevor Boyd Smith

Respuestas:


79

¿Ya ha mirado la documentación disponible en http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.html ? Si solo necesita que funcione en Windows, el segundo ejemplo parece ser exactamente lo que desea (si intercambia la ruta del directorio con la del archivo que desea ver).

De lo contrario, el sondeo probablemente será la única opción realmente independiente de la plataforma.

Nota: no he probado ninguna de estas soluciones.


55
Esta respuesta es específica de Windows, pero parece que algunas soluciones multiplataforma a este problema también se han publicado aquí.
Anderson Green

¿Existe un punto de referencia, si este proceso es más lento que implementarlo en un idioma nativo como c ++?
user1767754

Es mejor insertar el contenido relevante de las fuentes citadas, ya que pueden quedar desactualizadas.
Trilarion

2
(1.) al final de esta respuesta hay un fuerte descargo de responsabilidad ... "No he probado ninguna de estas soluciones". (2.) esta respuesta es más o menos una respuesta de "solo enlace" (3.) la respuesta menciona "sondeo" pero no proporciona agregar nada útil después de eso ... donde la respuesta de @ Deestan proporciona alguna buena información sobre sondeo
Trevor Boyd Smith

283

¿Intentaste usar Watchdog ?

Biblioteca Python API y utilidades de shell para monitorear eventos del sistema de archivos.

La supervisión del directorio es fácil con

  • Una API multiplataforma.
  • Una herramienta de shell para ejecutar comandos en respuesta a cambios en el directorio.

Comience rápidamente con un ejemplo simple en Inicio rápido ...


56
Instalable con easy_install? Cheque. ¿Licencia gratuita? Compruebe . Resuelve el problema en las grandes plataformas? Compruebe . Respaldo esta respuesta. Solo tenga en cuenta: el ejemplo en su página de proyecto no funciona fuera de la caja. Utilice el de su github en su lugar.
Inaimathi

66
Usamos perro guardián. Podemos cambiar a QFileSystemWatcher. Solo una advertencia justa: el perro guardián es bueno pero lejos de ser perfecto en todas las plataformas (en este momento). Cada sistema operativo tiene sus idiosincrasias. Entonces, a menos que se dedique a hacerlo perfecto, se arrancará el cabello. Si solo está buscando ver 10 archivos más o menos, lo encuestaré. El almacenamiento en caché de disco del sistema operativo es muy maduro y Watchdog implica sondear API de todos modos. Es principalmente para ver grandes estructuras de carpetas en mi humilde opinión.
SilentSteel

3
Mi única queja con watchdog es que tiene muchas dependencias. Menos que PyQt, por supuesto, pero no funciona y se siente como la solución mínima, la mejor práctica, hace un trabajo y lo hace bien.
AndreasT

1
¿Es correcto @denfromufa aquí? ¿Watchdog realmente bloquea los archivos, por lo que no se pueden editar simultáneamente para que watchdog los vea? Apenas puedo creer eso, sería completamente inútil.
Michel Müller

1
@ MichelMüller ¡Acabo de comprobar este ejemplo (ver enlace a continuación) y funciona! No estoy seguro de lo que estaba mal antes, pero esta respuesta no proporciona ningún ejemplo. stackoverflow.com/a/18599427/2230844
denfromufa

93

Si la encuesta es lo suficientemente buena para ti, solo miraría si cambia la estadística del archivo de "tiempo modificado". Para leerlo:

os.stat(filename).st_mtime

(Tenga en cuenta también que la solución de evento de cambio nativo de Windows no funciona en todas las circunstancias, por ejemplo, en unidades de red).

import os

class Monkey(object):
    def __init__(self):
        self._cached_stamp = 0
        self.filename = '/path/to/file'

    def ook(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...

1
¿Cómo puedes hacer esto en un intervalo?
dopatraman

2
@dopatraman Aquí es cómo puede hacer esto en un intervalo `import sys import time pub = Monkey () while True: try: time.sleep (1) pub.watch () excepto KeyboardInterrupt: print ('\ nDone') break excepto : print (f 'Error no controlado: {sys.exc_info () [0]}') `
Vlad Bezden

Gran solución simple! He añadido una comprobación para evitar que el archivo de informes cambiado en la primera ejecución: if self._cached_stamp is not None.
Noumenon

50

Si desea una solución multiplataforma, consulte QFileSystemWatcher . Aquí un código de ejemplo (no desinfectado):

from PyQt4 import QtCore

@QtCore.pyqtSlot(str)
def directory_changed(path):
    print('Directory Changed!!!')

@QtCore.pyqtSlot(str)
def file_changed(path):
    print('File Changed!!!')

fs_watcher = QtCore.QFileSystemWatcher(['/path/to/files_1', '/path/to/files_2', '/path/to/files_3'])

fs_watcher.connect(fs_watcher, QtCore.SIGNAL('directoryChanged(QString)'), directory_changed)
fs_watcher.connect(fs_watcher, QtCore.SIGNAL('fileChanged(QString)'), file_changed)

66
Creo que esta es posiblemente la mejor respuesta del grupo dado que a) confían en el objeto FileSystemwatcher de Win32 y no pueden ser portados o b) sondean el archivo (lo cual es malo para el rendimiento y no escalará). Es una pena que Python no tenga esta función incorporada, ya que PyQt es una gran dependencia si todo lo que está usando es la clase QFileSystemWatcher.
CadentOrange

44
Me gusta esta solución Quería señalar que necesitará una instancia de QApplication para que funcione, agregué "app = QtGui.QApplication (sys.argv)" justo debajo de las importaciones y luego "app.exec_ ()" después de las conexiones de señal.
spencewah

Solo probando esto en un cuadro de Linux, veo que se llama al método directorio_cambiado, pero no se cambia archivo_cambiado.
Ken Kinder

@CadentOrange, si no le gusta la dependencia de pyQt, el watchdogpaquete es la respuesta correcta
Mike Pennington el

¿Por qué no utilizar PySidepara eso en lugar de PyQtun uso tan pequeño?
Ciasto piekarz

29

No debería funcionar en Windows (¿tal vez con Cygwin?), Pero para usuarios de Unix, debe usar la llamada al sistema "fcntl". Aquí hay un ejemplo en Python. Es principalmente el mismo código si necesita escribirlo en C (los mismos nombres de función)

import time
import fcntl
import os
import signal

FNAME = "/HOME/TOTO/FILETOWATCH"

def handler(signum, frame):
    print "File %s modified" % (FNAME,)

signal.signal(signal.SIGIO, handler)
fd = os.open(FNAME,  os.O_RDONLY)
fcntl.fcntl(fd, fcntl.F_SETSIG, 0)
fcntl.fcntl(fd, fcntl.F_NOTIFY,
            fcntl.DN_MODIFY | fcntl.DN_CREATE | fcntl.DN_MULTISHOT)

while True:
    time.sleep(10000)

3
Funciona de maravilla con el kernel de Linux 2.6.31 en un sistema de archivos ext4 (en Ubuntu 10.04), aunque solo para directorios: genera un IOError "no un directorio" si lo uso con un archivo.
David Underhill

1
¡EXCELENTE! Lo mismo para mí, funciona solo para el directorio y ver archivos en este directorio. Pero no funcionará para archivos modificados en subdirectorios, por lo que parece que necesita recorrer los subdirectorios y verlos todos. (¿o hay una mejor manera de hacer esto?)
lfagundes

20

Echa un vistazo a pyinotify .

inotify reemplaza dnotify (de una respuesta anterior) en linuxes más nuevos y permite el monitoreo a nivel de archivo en lugar de a nivel de directorio.


55
No para frenar esta respuesta, pero después de leer este artículo, diría que puede no ser una solución tan glamorosa como se pensaba. serpentine.com/blog/2008/01/04/why-you-should-not-use-pyinotify
NuclearPeon

1
pyinotify tiene muchas desventajas que comienzan desde una base de código muy poco pitón hasta el consumo de memoria. Mejor buscar otras opciones ..
Tyto

13

Bueno, después de un poco de pirateo del guión de Tim Golden, tengo lo siguiente que parece funcionar bastante bien:

import os

import win32file
import win32con

path_to_watch = "." # look at the current directory
file_to_watch = "test.txt" # look for changes to a file called test.txt

def ProcessNewData( newData ):
    print "Text added: %s"%newData

# Set up the bits we'll need for output
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001
hDir = win32file.CreateFile (
  path_to_watch,
  FILE_LIST_DIRECTORY,
  win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
  None,
  win32con.OPEN_EXISTING,
  win32con.FILE_FLAG_BACKUP_SEMANTICS,
  None
)

# Open the file we're interested in
a = open(file_to_watch, "r")

# Throw away any exising log data
a.read()

# Wait for new data and call ProcessNewData for each new chunk that's written
while 1:
  # Wait for a change to occur
  results = win32file.ReadDirectoryChangesW (
    hDir,
    1024,
    False,
    win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
    None,
    None
  )

  # For each change, check to see if it's updating the file we're interested in
  for action, file in results:
    full_filename = os.path.join (path_to_watch, file)
    #print file, ACTIONS.get (action, "Unknown")
    if file == file_to_watch:
        newText = a.read()
        if newText != "":
            ProcessNewData( newText )

Probablemente podría funcionar con una carga más de comprobación de errores, pero para simplemente mirar un archivo de registro y realizar un procesamiento antes de escupirlo a la pantalla, esto funciona bien.

Gracias a todos por sus comentarios, ¡cosas geniales!


10

Para ver un solo archivo con sondeo y dependencias mínimas, aquí hay un ejemplo completamente desarrollado, basado en la respuesta de Deestan (arriba):

import os
import sys 
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_file, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self.filename = watch_file
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        stamp = os.stat(self.filename).st_mtime
        if stamp != self._cached_stamp:
            self._cached_stamp = stamp
            # File has changed, so do something...
            print('File changed')
            if self.call_func_on_change is not None:
                self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop        
    def watch(self):
        while self.running: 
            try: 
                # Look for changes
                time.sleep(self.refresh_delay_secs) 
                self.look() 
            except KeyboardInterrupt: 
                print('\nDone') 
                break 
            except FileNotFoundError:
                # Action on file not found
                pass
            except: 
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)

watch_file = 'my_file.txt'

# watcher = Watcher(watch_file)  # simple
watcher = Watcher(watch_file, custom_action, text='yes, changed')  # also call custom action function
watcher.watch()  # start the watch going

2
Se podría hacer watch_filey _cached_stampen listas, e iterar a través de ellos en un bucle. Sin embargo
4Oh4

¿No desencadena la acción cada vez que se ejecuta? _cached_stamp se establece en 0 y luego se compara con os.stat (self.filename) .st_mtime. _cached_stamp debe establecerse en os.stat (self.filename) .st_mtime en el constructor, ¿no?
Seanonymous

1
call_func_on_change()se activará en la primera ejecución de look(), pero luego _cached_stampse actualizará, por lo que no se activará nuevamente hasta que el valor de los os.stat(self.filename).st_mtime. _cached_stampcambios.
4Oh4

1
Puede establecer el valor de _cached_stampen el constructor si no desea call_func_on_change()ser llamado en la primera ejecución
4Oh4

He usado su script para llamar a alguna función en el cambio de archivo. Mi función no toma ningún argumento diferente al tuyo. Pensé que para hacerlo funcionar, necesito eliminar * args, ** kwargs Parecía que (ponía solo líneas con cambios): self.call_func_on_change(self) def custom_action(): watcher = Watcher(watch_file, custom_action())Pero esto no funcionó. Solo se llamó a la acción durante la primera iteración: el archivo cambió sí, cambió El archivo cambió El archivo cambió El archivo cambió Comenzó a funcionar cuando mantuve * args y lo llamé: watcher = Watcher(watch_file, custom_action)me cuesta preguntarme ¿por qué?
zwornik

7

Comprueba mi respuesta a una pregunta similar . Puedes probar el mismo bucle en Python. Esta página sugiere:

import time

while 1:
    where = file.tell()
    line = file.readline()
    if not line:
        time.sleep(1)
        file.seek(where)
    else:
        print line, # already has newline

También vea la pregunta tail () un archivo con Python .


Puedes sys.stdout.write (línea). Su código no funciona si el archivo está truncado. Python tiene un archivo de función incorporado ().
jfs

He publicado una versión modificada de su código. Puede incorporarlo en su respuesta si funciona para usted.
jfs

7

La solución más simple para mí es usar watchmedo, la herramienta de watchdog

Desde https://pypi.python.org/pypi/watchdog ahora tengo un proceso que busca los archivos sql en un directorio y los ejecuta si es necesario.

watchmedo shell-command \
--patterns="*.sql" \
--recursive \
--command='~/Desktop/load_files_into_mysql_database.sh' \
.

6

Bueno, como estás usando Python, puedes abrir un archivo y seguir leyendo líneas.

f = open('file.log')

Si la lectura de línea no está vacía , la procesa.

line = f.readline()
if line:
    // Do what you want with the line

Puede faltar que está bien seguir llamando readlineal EOF. Seguirá devolviendo una cadena vacía en este caso. Y cuando se agrega algo al archivo de registro, la lectura continuará desde donde se detuvo, según lo necesite.

Si está buscando una solución que use eventos o una biblioteca en particular, especifique esto en su pregunta. De lo contrario, creo que esta solución está bien.


6

Aquí hay una versión simplificada del código de Kender que parece hacer el mismo truco y no importa todo el archivo:

# Check file for new data.

import time

f = open(r'c:\temp\test.txt', 'r')

while True:

    line = f.readline()
    if not line:
        time.sleep(1)
        print 'Nothing New'
    else:
        print 'Call Function: ', line

6

Esta es otra modificación del script de Tim Goldan que se ejecuta en tipos de Unix y agrega un simple observador para la modificación de archivos mediante el uso de un dict (file => time).

uso: whateverName.py path_to_dir_to_watch

#!/usr/bin/env python

import os, sys, time

def files_to_timestamp(path):
    files = [os.path.join(path, f) for f in os.listdir(path)]
    return dict ([(f, os.path.getmtime(f)) for f in files])

if __name__ == "__main__":

    path_to_watch = sys.argv[1]
    print('Watching {}..'.format(path_to_watch))

    before = files_to_timestamp(path_to_watch)

    while 1:
        time.sleep (2)
        after = files_to_timestamp(path_to_watch)

        added = [f for f in after.keys() if not f in before.keys()]
        removed = [f for f in before.keys() if not f in after.keys()]
        modified = []

        for f in before.keys():
            if not f in removed:
                if os.path.getmtime(f) != before.get(f):
                    modified.append(f)

        if added: print('Added: {}'.format(', '.join(added)))
        if removed: print('Removed: {}'.format(', '.join(removed)))
        if modified: print('Modified: {}'.format(', '.join(modified)))

        before = after

Actualizado para admitir python3
ronedg

4

Como puede ver en el artículo de Tim Golden , señalado por Horst Gutmann , WIN32 es relativamente complejo y observa directorios, no un solo archivo.

Me gustaría sugerirle que busque en IronPython , que es una implementación de Python .NET . Con IronPython puedes usar toda la funcionalidad .NET , incluyendo

System.IO.FileSystemWatcher

Que maneja archivos individuales con una simple interfaz de eventos .


@Ciasto porque entonces debes tener Iron Python disponible en lugar de una instalación básica de Python.
Jon Cage

1

Este es un ejemplo de comprobación de cambios en un archivo. Una que puede no ser la mejor manera de hacerlo, pero seguramente es un camino corto.

Herramienta práctica para reiniciar la aplicación cuando se han realizado cambios en la fuente. Hice esto cuando jugaba con pygame para poder ver los efectos inmediatamente después de guardar el archivo.

Cuando se usa en pygame, asegúrese de que las cosas en el bucle 'while' se coloquen en su bucle de juego, también conocido como actualización o lo que sea. De lo contrario, su aplicación se atascará en un bucle infinito y no verá la actualización de su juego.

file_size_stored = os.stat('neuron.py').st_size

  while True:
    try:
      file_size_current = os.stat('neuron.py').st_size
      if file_size_stored != file_size_current:
        restart_program()
    except: 
      pass

En caso de que quisieras el código de reinicio que encontré en la web. Aquí está. (No es relevante para la pregunta, aunque podría ser útil)

def restart_program(): #restart application
    python = sys.executable
    os.execl(python, python, * sys.argv)

Diviértete haciendo que los electrones hagan lo que quieres que hagan.


Parece que usar en .st_mtimelugar de .st_sizesería más confiable y una forma igualmente corta de hacerlo, aunque el OP ha indicado que no quería hacerlo a través de encuestas.
Martineau

1
ACTIONS = {
  1 : "Created",
  2 : "Deleted",
  3 : "Updated",
  4 : "Renamed from something",
  5 : "Renamed to something"
}
FILE_LIST_DIRECTORY = 0x0001

class myThread (threading.Thread):
    def __init__(self, threadID, fileName, directory, origin):
        threading.Thread.__init__(self)
        self.threadID = threadID
        self.fileName = fileName
        self.daemon = True
        self.dir = directory
        self.originalFile = origin
    def run(self):
        startMonitor(self.fileName, self.dir, self.originalFile)

def startMonitor(fileMonitoring,dirPath,originalFile):
    hDir = win32file.CreateFile (
        dirPath,
        FILE_LIST_DIRECTORY,
        win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE,
        None,
        win32con.OPEN_EXISTING,
        win32con.FILE_FLAG_BACKUP_SEMANTICS,
        None
    )
    # Wait for new data and call ProcessNewData for each new chunk that's
    # written
    while 1:
        # Wait for a change to occur
        results = win32file.ReadDirectoryChangesW (
            hDir,
            1024,
            False,
            win32con.FILE_NOTIFY_CHANGE_LAST_WRITE,
            None,
            None
        )
        # For each change, check to see if it's updating the file we're
        # interested in
        for action, file_M in results:
            full_filename = os.path.join (dirPath, file_M)
            #print file, ACTIONS.get (action, "Unknown")
            if len(full_filename) == len(fileMonitoring) and action == 3:
                #copy to main file
                ...

1

Aquí hay un ejemplo orientado a ver archivos de entrada que escriben no más de una línea por segundo, pero generalmente mucho menos. El objetivo es agregar la última línea (escritura más reciente) al archivo de salida especificado. Copié esto de uno de mis proyectos y eliminé todas las líneas irrelevantes. Tendrá que completar o cambiar los símbolos que faltan.

from PyQt5.QtCore import QFileSystemWatcher, QSettings, QThread
from ui_main_window import Ui_MainWindow   # Qt Creator gen'd 

class MainWindow(QMainWindow, Ui_MainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        Ui_MainWindow.__init__(self)
        self._fileWatcher = QFileSystemWatcher()
        self._fileWatcher.fileChanged.connect(self.fileChanged)

    def fileChanged(self, filepath):
        QThread.msleep(300)    # Reqd on some machines, give chance for write to complete
        # ^^ About to test this, may need more sophisticated solution
        with open(filepath) as file:
            lastLine = list(file)[-1]
        destPath = self._filemap[filepath]['dest file']
        with open(destPath, 'a') as out_file:               # a= append
            out_file.writelines([lastLine])

Por supuesto, la clase QMainWindow que abarca no es estrictamente necesaria, es decir. puede usar QFileSystemWatcher solo.


0

También puede usar una biblioteca simple llamada repyt , aquí hay un ejemplo:

repyt ./app.py

0

Parece que nadie ha publicado fswatch . Es un observador de sistemas de archivos multiplataforma. Simplemente instálelo, ejecútelo y siga las instrucciones.

Lo he usado con programas de python y golang y simplemente funciona.


0

Solución @ 4Oh4 relacionada: un cambio suave para una lista de archivos para ver;

import os
import sys
import time

class Watcher(object):
    running = True
    refresh_delay_secs = 1

    # Constructor
    def __init__(self, watch_files, call_func_on_change=None, *args, **kwargs):
        self._cached_stamp = 0
        self._cached_stamp_files = {}
        self.filenames = watch_files
        self.call_func_on_change = call_func_on_change
        self.args = args
        self.kwargs = kwargs

    # Look for changes
    def look(self):
        for file in self.filenames:
            stamp = os.stat(file).st_mtime
            if not file in self._cached_stamp_files:
                self._cached_stamp_files[file] = 0
            if stamp != self._cached_stamp_files[file]:
                self._cached_stamp_files[file] = stamp
                # File has changed, so do something...
                file_to_read = open(file, 'r')
                value = file_to_read.read()
                print("value from file", value)
                file_to_read.seek(0)
                if self.call_func_on_change is not None:
                    self.call_func_on_change(*self.args, **self.kwargs)

    # Keep watching in a loop
    def watch(self):
        while self.running:
            try:
                # Look for changes
                time.sleep(self.refresh_delay_secs)
                self.look()
            except KeyboardInterrupt:
                print('\nDone')
                break
            except FileNotFoundError:
                # Action on file not found
                pass
            except Exception as e:
                print(e)
                print('Unhandled error: %s' % sys.exc_info()[0])

# Call this function each time a change happens
def custom_action(text):
    print(text)
    # pass

watch_files = ['/Users/mexekanez/my_file.txt', '/Users/mexekanez/my_file1.txt']

# watcher = Watcher(watch_file)  # simple



if __name__ == "__main__":
    watcher = Watcher(watch_files, custom_action, text='yes, changed')  # also call custom action function
    watcher.watch()  # start the watch going


-2

No conozco ninguna función específica de Windows. Puede intentar obtener el hash MD5 del archivo cada segundo / minuto / hora (depende de qué tan rápido lo necesite) y compararlo con el último hash. Cuando difiere, sabe que el archivo ha cambiado y lee las líneas más recientes.


-6

Intentaría algo como esto.

    try:
            f = open(filePath)
    except IOError:
            print "No such file: %s" % filePath
            raw_input("Press Enter to close window")
    try:
            lines = f.readlines()
            while True:
                    line = f.readline()
                    try:
                            if not line:
                                    time.sleep(1)
                            else:
                                    functionThatAnalisesTheLine(line)
                    except Exception, e:
                            # handle the exception somehow (for example, log the trace) and raise the same exception again
                            raw_input("Press Enter to close window")
                            raise e
    finally:
            f.close()

El bucle verifica si hay una nueva línea (s) desde la última vez que se leyó el archivo; si es así, se lee y se pasa a la functionThatAnalisesTheLinefunción. Si no, el script espera 1 segundo y vuelve a intentar el proceso.


44
-1: Abrir el archivo y leer las líneas no es una gran idea cuando los archivos pueden ser de 100 MB. Tendría que ejecutarlo para todos y cada uno de los archivos, lo que sería malo cuando desee ver miles de archivos.
Jon Cage el

1
De Verdad? ¿Abrir el archivo para cambios?
Farsheed
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.