Я предпочитаю работать с Python, потому что это очень гибкий язык
программирования, который позволяет мне легко взаимодействовать с
операционной системой. Это также включает функции файловой системы.
Чтобы просто перечислить файлы в каталоге, в pathlib
вступают os
,
subprocess
, fnmatch
и pathlib. Следующие ниже решения
демонстрируют, как эффективно использовать эти методы.
Использование os.walk()
Модуль os
содержит длинный список методов, которые работают с файловой
системой и операционной системой. Один из них - walk()
, который
генерирует имена файлов в дереве каталогов, перемещаясь по дереву либо
сверху вниз, либо снизу вверх (с настройкой по умолчанию сверху вниз).
os.walk()
возвращает список из трех элементов. Он содержит имя
корневого каталога, список имен подкаталогов и список имен файлов в
текущем каталоге. В листинге 1 показано, как написать это с помощью
всего трех строк кода. Это работает с интерпретаторами Python 2 и 3.
Листинг 1: Обход текущего каталога с помощью os.walk()
import os
for root, dirs, files in os.walk("."):
for filename in files:
print(filename)
Использование командной строки через подпроцесс
Примечание . Хотя это допустимый способ перечисления файлов в каталоге, его не рекомендуется использовать, поскольку он дает возможность атак путем внедрения команд.
Как уже было описано в статье Параллельная обработка в
Python , subprocess
позволяет
выполнять системную команду и собирать ее результат. Системная команда,
которую мы вызываем в этом случае, следующая:
Пример 1: Список файлов в текущем каталоге
$ ls -p . | grep -v /$
Команда ls -p .
перечисляет файлы каталогов для текущего каталога и
добавляет разделитель /
в конце имени каждого подкаталога, который нам
понадобится на следующем шаге. Выходные данные этого вызова передаются
команде grep
которая фильтрует данные по мере необходимости.
Параметры -v /$
исключают все имена записей, заканчивающиеся
разделителем /
. Фактически, /$
- это регулярное выражение, которое
соответствует всем строкам, содержащим символ /
как самый последний
символ перед концом строки, представленной символом $
.
Модуль subprocess
позволяет создавать реальные каналы и соединять
потоки ввода и вывода, как в командной строке. Вызов метода
subprocess.Popen()
открывает соответствующий процесс и определяет два
параметра с именами stdin и stdout .
В листинге 2 показано, как это запрограммировать. Первая переменная
ls
определяется как процесс, выполняющий ls -p .
что выводит в
трубу. Вот почему канал stdout определен как subprocess.PIPE
. Вторая
переменная grep
тоже определяется как процесс, но вместо этого
grep -v /$
Чтобы прочитать вывод команды ls
из конвейера, канал stdin grep
определяется как ls.stdout
. Наконец, переменная endOfPipe
считывает
вывод grep
из grep.stdout
который выводится на стандартный вывод
поэлементно в for
ниже. Результат показан в Примере 2 .
Листинг 2: Определение двух процессов, связанных с конвейером
import subprocess
# define the ls command
ls = subprocess.Popen(["ls", "-p", "."],
stdout=subprocess.PIPE,
)
# define the grep command
grep = subprocess.Popen(["grep", "-v", "/$"],
stdin=ls.stdout,
stdout=subprocess.PIPE,
)
# read from the end of the pipe (stdout)
endOfPipe = grep.stdout
# output the files line by line
for line in endOfPipe:
print (line)
Пример 2: Запуск программы
$ python find-files3.py
find-files2.py
find-files3.py
find-files4.py
...
Это решение хорошо работает как с Python 2, так и с Python 3, но можем ли мы как-то его улучшить? Тогда давайте посмотрим на другие варианты.
Объединение os
и fnmatch
Как вы видели ранее, решение, использующее подпроцессы, элегантно, но
требует большого количества кода. Вместо этого давайте объединим методы
из двух модулей os
и fnmatch
. Этот вариант работает также с Python
2 и 3.
В качестве первого шага мы импортируем два модуля os
и fnmatch
.
Затем мы определяем каталог, в котором мы хотели бы перечислить файлы,
используя os.listdir()
, а также шаблон, по которому файлы нужно
фильтровать. В for
мы перебираем список записей, хранящихся в
переменной listOfFiles
.
Наконец, с помощью fnmatch
мы фильтруем записи, которые ищем, и
выводим соответствующие записи в стандартный вывод. В листинге 3
содержится сценарий Python, а в примере 3 - соответствующие выходные
данные.
Листинг 3: Список файлов с использованием модуля os и fnmatch
import os, fnmatch
listOfFiles = os.listdir('.')
pattern = "*.py"
for entry in listOfFiles:
if fnmatch.fnmatch(entry, pattern):
print (entry)
Пример 3: результат листинга 3
$ python2 find-files.py
find-files.py
find-files2.py
find-files3.py
...
Использование os.listdir()
и генераторов
Проще говоря, генератор - это мощный итератор, который сохраняет свое состояние. Чтобы узнать больше о генераторах, ознакомьтесь с одной из наших предыдущих статей, « Генераторы Python» .
Следующий вариант сочетает в себе метод listdir()
os
с функцией
генератора. Код работает с обеими версиями Python 2 и 3.
Как вы могли заметить ранее, метод listdir()
возвращает список записей
для данного каталога. Метод os.path.isfile()
возвращает True
если
данная запись является файлом. Оператор yield
завершает функцию, но
сохраняет текущее состояние и возвращает только имя записи, обнаруженной
как файл. Это позволяет нам перебрать функцию генератора (см. Листинг
4 ). Вывод такой же, как в Примере 3 .
Листинг 4: Объединение os.listdir()
и функции генератора
import os
def files(path):
for file in os.listdir(path):
if os.path.isfile(os.path.join(path, file)):
yield file
for file in files("."):
print (file)
Используйте pathlib
Модуль pathlib
описывает себя как способ «анализировать, создавать,
тестировать и иным образом работать с именами файлов и путями с
использованием объектно-ориентированного API вместо низкоуровневых
строковых операций». Звучит круто - давайте сделаем это. Начиная с
Python 3, модуль входит в стандартный дистрибутив.
В листинге 5 мы сначала определяем каталог. Точка (".") Определяет
текущий каталог. Затем метод iterdir()
возвращает итератор, который
возвращает имена всех файлов. В for
мы печатаем имена файлов один за
другим.
Листинг 5: Чтение содержимого каталога с помощью pathlib
import pathlib
# define the path
currentDirectory = pathlib.Path('.')
for currentFile in currentDirectory.iterdir():
print(currentFile)
Опять же, результат такой же, как в Примере 3 .
В качестве альтернативы мы можем извлекать файлы, сопоставляя их имена с помощью так называемого глобуса . Таким образом, мы можем получить только те файлы, которые нам нужны. Например, в приведенном ниже коде мы хотим только перечислить файлы Python в нашем каталоге, что мы делаем, указав «* .py» в глобусе.
Листинг 6: Использование pathlib
с методом glob
import pathlib
# define the path
currentDirectory = pathlib.Path('.')
# define the pattern
currentPattern = "*.py"
for currentFile in currentDirectory.glob(currentPattern):
print(currentFile)
Использование os.scandir()
В Python 3.6 новый метод становится доступен в модуле os
Он называется
scandir()
и значительно упрощает вызов списка файлов в каталоге.
Сначала импортировав os
, используйте метод getcwd()
для определения
текущего рабочего каталога и сохраните это значение в переменной path
Затем scandir()
возвращает список записей для этого пути, который мы
проверяем на предмет наличия файла с помощью is_file()
.
Листинг 7: Чтение содержимого каталога с помощью scandir()
import os
# detect the current working directory
path = os.getcwd()
# read the entries
with os.scandir(path) as listOfEntries:
for entry in listOfEntries:
# print all entries that are files
if entry.is_file():
print(entry.name)
И снова вывод в листинге 7 идентичен выводу из примера 3 .
Заключение
Есть разногласия, какая версия лучшая, какая самая элегантная, а какая
самая «питоническая». Мне нравится простота метода os.walk()
а также
использование модулей fnmatch
и pathlib
Две версии с процессами / конвейером и итератором требуют более глубокого понимания процессов UNIX и знаний Python, поэтому они могут быть не лучшими для всех программистов из-за их дополнительной (и ненужной) сложности.
Чтобы найти ответ на вопрос, какая версия самая быстрая, очень timeit
модуль timeit. Этот модуль считает время, прошедшее между двумя
событиями.
Чтобы сравнить все наши решения без их изменения, мы используем функциональность Python: вызываем интерпретатор Python с именем модуля и соответствующим кодом Python, который нужно выполнить. Чтобы сделать это сразу для всех сценариев Python, помогает сценарий оболочки ( листинг 8 ).
Листинг 8: Оценка времени выполнения с timeit
модуля timeit
#! /bin/bash
for filename in *.py; do
echo "$filename:"
cat $filename | python3 -m timeit
echo " "
done
Тесты проводились с использованием Python 3.5.3. Результат выглядит
следующим образом, тогда как os.walk()
дает лучший результат.
Выполнение тестов с Python 2 возвращает разные значения, но не меняет
порядок - os.walk()
по-прежнему находится наверху списка.
Методика Результат для 100000000 циклов
os.walk 0,0085 мксек на цикл подпроцесс / труба 0,00859 мксек на цикл os.listdir / fnmatch 0,00912 мксек на цикл os.listdir / генератор 0,00867 мксек на цикл pathlib 0,00854 мксек на цикл pathlib / glob 0,00858 мксек на цикл os.scandir 0,00856 мксек на цикл
Благодарности
Автор хотел бы поблагодарить Герольда Рупрехта за его поддержку и комментарии при подготовке этой статьи.