Pregunta

si llamo os.stat() en un roto symlink, Python lanza un OSError excepción.Esto lo hace útil para encontrarlos.Sin embargo, hay algunas otras razones que os.stat() podría generar una excepción similar.¿Existe una forma más precisa de detectar roturas? symlinks con Python bajo Linux?

¿Fue útil?

Solución

Un dicho común en Python es que es más fácil pedir perdón que permiso.Si bien no soy partidario de esta afirmación en la vida real, se aplica en muchos casos.Por lo general, desea evitar el código que encadena dos llamadas al sistema en el mismo archivo, porque nunca se sabe qué sucederá con el archivo entre las dos llamadas en su código.

Un error típico es escribir algo como:

if os.path.exists(path):
    os.unlink(path)

La segunda llamada (os.unlink) puede fallar si algo más la eliminó después de su prueba if, generó una excepción y detuvo la ejecución del resto de su función.(Se podría pensar que esto no sucede en la vida real, pero la semana pasada detectamos otro error como ese de nuestro código base, y fue el tipo de error que dejó a algunos programadores rascándose la cabeza y reclamando 'Heisenbug' para el Los últimos meses)

Entonces, en tu caso particular, probablemente haría:

try:
    os.stat(path)
except OSError, e:
    if e.errno == errno.ENOENT:
        print 'path %s does not exist or is a broken symlink' % path
    else:
        raise e

La molestia aquí es que la estadística devuelve el mismo código de error para un enlace simbólico que simplemente no está ahí y un enlace simbólico roto.

Entonces, supongo que no tienes más remedio que romper la atomicidad y hacer algo como

if not os.path.exists(os.readlink(path)):
    print 'path %s is a broken symlink' % path

Otros consejos

sistema operativo.lstat() puede ser útil.Si lstat() tiene éxito y stat() falla, entonces probablemente sea un enlace roto.

Esto no es atómico pero funciona.

os.path.islink(filename) and not os.path.exists(filename)

De hecho por RTFM(leyendo el fantástico manual) vemos

sistema operativo.ruta.existe(ruta)

Devuelve True si la ruta hace referencia a una ruta existente.Devuelve False para enlaces simbólicos rotos.

También dice:

En algunas plataformas, esta función puede devolver False si no se otorga permiso para ejecutar os.stat() en el archivo solicitado, incluso si la ruta existe físicamente.

Entonces, si le preocupan los permisos, debería agregar otras cláusulas.

¿Puedo mencionar las pruebas de enlaces duros sin Python?/bin/test tiene la condición FILE1 -ef FILE2 que es verdadera cuando los archivos comparten un inodo.

Por lo tanto, algo como find . -type f -exec test \{} -ef /path/to/file \; -print Funciona para pruebas de enlaces físicos a un archivo específico.

Lo que me lleva a leer man test y las menciones de -L y -h que funcionan en un archivo y devuelven verdadero si ese archivo es un enlace simbólico, sin embargo, eso no le indica si falta el objetivo.

encontré eso head -0 FILE1 devolvería un código de salida de 0 si el archivo se puede abrir y un 1 si no puede, lo que en el caso de un enlace simbólico a un archivo normal funciona como prueba para saber si el destino se puede leer.

os.ruta

Puede intentar usar realpath() para obtener a qué apunta el enlace simbólico y luego intentar determinar si es un archivo válido usando su archivo.

(No puedo probarlo en este momento, así que tendrás que probarlo y ver qué obtienes)

No soy un tipo de Python pero parece os.readlink().La lógica que usaría en Perl es usar readlink() para encontrar el objetivo y usar stat() para probar y ver si el objetivo existe.

Editar:Saqué algo de Perl que muestra el enlace de lectura.Creo que stat y readlink de Perl y os.stat() y os.readlink() de python son envoltorios para las llamadas al sistema, por lo que esto debería traducirse razonablemente bien como código de prueba de concepto:

wembley 0 /home/jj33/swap > cat p
my $f = shift;

while (my $l = readlink($f)) {
  print "$f -> $l\n";
  $f = $l;
}

if (!-e $f) {
  print "$f doesn't exist\n";
}
wembley 0 /home/jj33/swap > ls -l | grep ^l
lrwxrwxrwx    1 jj33  users          17 Aug 21 14:30 link -> non-existant-file
lrwxrwxrwx    1 root     users          31 Oct 10  2007 mm -> ../systems/mm/20071009-rewrite//
lrwxrwxrwx    1 jj33  users           2 Aug 21 14:34 mmm -> mm/
wembley 0 /home/jj33/swap > perl p mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p mmm
mmm -> mm
mm -> ../systems/mm/20071009-rewrite/
wembley 0 /home/jj33/swap > perl p link
link -> non-existant-file
non-existant-file doesn't exist
wembley 0 /home/jj33/swap >

Tuve un problema similar:¿Cómo detectar enlaces simbólicos rotos, incluso cuando ocurren en algún directorio principal?También quería registrarlos todos (en una aplicación que maneja una cantidad bastante grande de archivos), pero sin demasiadas repeticiones.

Esto es lo que se me ocurrió, incluidas las pruebas unitarias.

archivoutil.py:

import os
from functools import lru_cache
import logging

logger = logging.getLogger(__name__)

@lru_cache(maxsize=2000)
def check_broken_link(filename):
    """
    Check for broken symlinks, either at the file level, or in the
    hierarchy of parent dirs.
    If it finds a broken link, an ERROR message is logged.
    The function is cached, so that the same error messages are not repeated.

    Args:
        filename: file to check

    Returns:
        True if the file (or one of its parents) is a broken symlink.
        False otherwise (i.e. either it exists or not, but no element
        on its path is a broken link).

    """
    if os.path.isfile(filename) or os.path.isdir(filename):
        return False
    if os.path.islink(filename):
        # there is a symlink, but it is dead (pointing nowhere)
        link = os.readlink(filename)
        logger.error('broken symlink: {} -> {}'.format(filename, link))
        return True
    # ok, we have either:
    #   1. a filename that simply doesn't exist (but the containing dir
           does exist), or
    #   2. a broken link in some parent dir
    parent = os.path.dirname(filename)
    if parent == filename:
        # reached root
        return False
    return check_broken_link(parent)

Pruebas unitarias:

import logging
import shutil
import tempfile
import os

import unittest
from ..util import fileutil


class TestFile(unittest.TestCase):

    def _mkdir(self, path, create=True):
        d = os.path.join(self.test_dir, path)
        if create:
            os.makedirs(d, exist_ok=True)
        return d

    def _mkfile(self, path, create=True):
        f = os.path.join(self.test_dir, path)
        if create:
            d = os.path.dirname(f)
            os.makedirs(d, exist_ok=True)
            with open(f, mode='w') as fp:
                fp.write('hello')
        return f

    def _mklink(self, target, path):
        f = os.path.join(self.test_dir, path)
        d = os.path.dirname(f)
        os.makedirs(d, exist_ok=True)
        os.symlink(target, f)
        return f

    def setUp(self):
        # reset the lru_cache of check_broken_link
        fileutil.check_broken_link.cache_clear()

        # create a temporary directory for our tests
        self.test_dir = tempfile.mkdtemp()

        # create a small tree of dirs, files, and symlinks
        self._mkfile('a/b/c/foo.txt')
        self._mklink('b', 'a/x')
        self._mklink('b/c/foo.txt', 'a/f')
        self._mklink('../..', 'a/b/c/y')
        self._mklink('not_exist.txt', 'a/b/c/bad_link.txt')
        bad_path = self._mkfile('a/XXX/c/foo.txt', create=False)
        self._mklink(bad_path, 'a/b/c/bad_path.txt')
        self._mklink('not_a_dir', 'a/bad_dir')

    def tearDown(self):
        # Remove the directory after the test
        shutil.rmtree(self.test_dir)

    def catch_check_broken_link(self, expected_errors, expected_result, path):
        filename = self._mkfile(path, create=False)
        with self.assertLogs(level='ERROR') as cm:
            result = fileutil.check_broken_link(filename)
            logging.critical('nothing')  # trick: emit one extra message, so the with assertLogs block doesn't fail
        error_logs = [r for r in cm.records if r.levelname is 'ERROR']
        actual_errors = len(error_logs)
        self.assertEqual(expected_result, result, msg=path)
        self.assertEqual(expected_errors, actual_errors, msg=path)

    def test_check_broken_link_exists(self):
        self.catch_check_broken_link(0, False, 'a/b/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/x/c/foo.txt')
        self.catch_check_broken_link(0, False, 'a/f')
        self.catch_check_broken_link(0, False, 'a/b/c/y/b/c/y/b/c/foo.txt')

    def test_check_broken_link_notfound(self):
        self.catch_check_broken_link(0, False, 'a/b/c/not_found.txt')

    def test_check_broken_link_badlink(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_link.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_link.txt')

    def test_check_broken_link_badpath(self):
        self.catch_check_broken_link(1, True, 'a/b/c/bad_path.txt')
        self.catch_check_broken_link(0, True, 'a/b/c/bad_path.txt')

    def test_check_broken_link_badparent(self):
        self.catch_check_broken_link(1, True, 'a/bad_dir/c/foo.txt')
        self.catch_check_broken_link(0, True, 'a/bad_dir/c/foo.txt')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir/c')
        # bad link, but shouldn't log a new error:
        self.catch_check_broken_link(0, True, 'a/bad_dir')

if __name__ == '__main__':
    unittest.main()
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top