Избегайте бесконечной рекурсии с помощью os.walk

Я использую os.walk с followlinks=True, но я попал в место, где символическая ссылка ссылается на свой собственный каталог, вызывая бесконечный цикл. Виновником в этом случае является /usr/bin/X11, список которых приведен ниже:

lrwxrwxrwx 1 root root           1 Apr 24  2015 X11 -> .

Есть ли способ избежать перехода по ссылкам на . или .., которые, как я полагаю, вызовут аналогичные проблемы? Думаю, я мог бы проверить это с помощью os.readlink, а затем сравнить с текущим путем. Есть ли другое решение для этого?


person Eric    schedule 02.05.2016    source источник
comment
Как насчет ссылок типа a -> b и b -> a?   -  person Jérôme    schedule 02.05.2016
comment
Да, это, вероятно, также вызовет большие проблемы. Например, поддерживать список искомых каталогов, который быстро становится большим и уродливым.   -  person Eric    schedule 02.05.2016
comment
@Eric: Почему это должно быть некрасиво?   -  person Dietrich Epp    schedule 02.05.2016


Ответы (2)


Невозможно избежать хранения набора всех посещенных каталогов, если вы хотите избежать рекурсии. Вам не нужно использовать readlink, однако вы можете просто хранить иноды. Это позволяет полностью избежать проблемы канонизации пути.

import os
dirs = set()
for dirpath, dirnames, filenames in os.walk('.', followlinks=True):
    st = os.stat(dirpath)
    scandirs = []
    for dirname in dirnames:
        st = os.stat(os.path.join(dirpath, dirname))
        dirkey = st.st_dev, st.st_ino
        if dirkey not in dirs:
            dirs.add(dirkey)
            scandirs.append(dirname)
    dirnames[:] = scandirs
    print(dirpath)
person Dietrich Epp    schedule 02.05.2016
comment
Хорошо, не надо быть уродливым =) - person Eric; 02.05.2016
comment
Разве это не рискованно, если ваши символические ссылки пересекают границы файловой системы? У вас могут быть разные файлы с одним и тем же индексом в двух разных файловых системах, не так ли? - person gimboland; 28.08.2017
comment
@gimboland: Посмотрите на код: dirkey = st.st_dev, st.st_ino. - person Dietrich Epp; 28.08.2017
comment
Ах да, извините, я пропустил это; хороший. - person gimboland; 29.08.2017
comment
Что, если я не возражаю против того, чтобы один и тот же каталог включался несколько раз с помощью символических ссылок, но хотел бы избежать рекурсии? - person Ivan; 27.11.2018
comment
@Ivan: Тогда вам ничего не нужно из этого ответа, вы можете просто использовать обычный os.walk() сам по себе. - person Dietrich Epp; 27.11.2018
comment
В документации @DietrichEpp os.walk() явно упоминается, что он уязвим для рекурсивных символических ссылок с followlinks=True. - person Ivan; 27.11.2018
comment
@Ivan: с функцией os.walk это не так просто. Вам нужно будет только проверять родительские каталоги, что требует таких действий, как поддержание отдельного стека инодов (и определение того, сколько вам нужно извлекать каждый раз в цикле), поддержание карты от путей к инодам (и переход вверх по ним). дерево), или написание собственной версии os.walk. - person Dietrich Epp; 27.11.2018
comment
@DietrichEpp Я думал о преобразовании полного пути каждого элемента, который os.walk() находит, в список инодов и отбрасывании элементов, которые содержат себя на пути к себе таким образом, но быстро отклонил эту идею, поняв, что это не помешает os.walk () сам от продолжения прохождения рекурсивного пути. Теперь единственная идея, которая у меня есть, это дать os.walk и реализовать все это вручную с помощью os.listdir(). - person Ivan; 27.11.2018
comment
@Ivan: Не отказывайтесь от этой идеи так быстро, os.walk не будет проходить рекурсивный путь, если вы его удалите. Это то, что делает строка dirnames[:] = scandirs в приведенном выше примере. - person Dietrich Epp; 27.11.2018
comment
@DietrichEpp Спасибо. Я попробую это. Кстати, почему dirnames[:] = scandirs, а не dirnames = scandirs? - person Ivan; 27.11.2018
comment
Это изменяет существующий список. - person Dietrich Epp; 27.11.2018

Чтобы полностью избежать проблемы бесконечной рекурсии (со ссылками, указывающими куда угодно), вам нужно хранить файлы и/или каталоги, которые вы уже посещали.

Люди из модуля pynotify столкнулись с той же проблемой и использовали описанный метод. Патч по ссылке ;)

person salomonderossi    schedule 02.05.2016