Автор оригинала: Doug Hellmann.
Цель:
Создавайте программы фильтрации командной строки для обработки строк из входных потоков.
Модуль fileinput
– это платформа для создания программ командной строки для обработки текстовых файлов в качестве фильтра.
Преобразование файлов M3U в RSS
Примером фильтра является m3utorss , программа для преобразования набора файлов MP3 в RSS-канал, который можно использовать как подкаст. Входными данными для программы являются один или несколько файлов m3u, в которых перечислены файлы MP3, подлежащие распространению. Результатом является RSS-канал, выводимый на консоль. Чтобы обработать ввод, программе необходимо перебрать список имен файлов и
- Откройте каждый файл.
- Прочтите каждую строку файла.
- Выясните, относится ли эта строка к mp3-файлу.
- Если это так, добавьте новый элемент в RSS-канал.
- Распечатайте результат.
Вся эта обработка файлов могла быть написана вручную. Это не так сложно, и после некоторого тестирования даже обработка ошибок будет правильной. Но fileinput
обрабатывает все детали, поэтому программа упрощается.
for line in fileinput.input(sys.argv[1:]): mp3filename line.strip() if not mp3filename or mp3filename.startswith('#'): continue item SubElement(rss, 'item') title SubElement(item, 'title') title.text mp3filename encl SubElement(item, 'enclosure', {'type': 'audio/mpeg', 'url': mp3filename})
Функция input ()
принимает в качестве аргумента список имен файлов для проверки. Если список пуст, модуль считывает данные со стандартного ввода. Функция возвращает итератор, который создает отдельные строки из обрабатываемых текстовых файлов. Вызывающему абоненту просто нужно перебрать каждую строку, пропуская пробелы и комментарии, чтобы найти ссылки на файлы MP3.
Вот полная программа.
fileinput_example.py
import fileinput import sys import time from xml.etree.ElementTree import Element, SubElement, tostring from xml.dom import minidom # Establish the RSS and channel nodes rss Element('rss', {'xmlns:dc': "http://purl.org/dc/elements/1.1/", 'version': '2.0'}) channel SubElement(rss, 'channel') title SubElement(channel, 'title') title.text 'Sample podcast feed' desc SubElement(channel, 'description') desc.text 'Generated for PyMOTW' pubdate SubElement(channel, 'pubDate') pubdate.text time.asctime() gen SubElement(channel, 'generator') gen.text 'https://pymotw.com/' for line in fileinput.input(sys.argv[1:]): mp3filename line.strip() if not mp3filename or mp3filename.startswith('#'): continue item SubElement(rss, 'item') title SubElement(item, 'title') title.text mp3filename encl SubElement(item, 'enclosure', {'type': 'audio/mpeg', 'url': mp3filename}) rough_string tostring(rss) reparsed minidom.parseString(rough_string) print(reparsed.toprettyxml(indent" "))
Этот пример входного файла содержит имена нескольких файлов MP3.
sample_data.m3u
# This is a sample m3u file episode-one.mp3 episode-two.mp3
Запуск fileinput_example.py
с образцом входных данных создает данные XML с использованием формата RSS.
$ python3 fileinput_example.py sample_data.m3uSample podcast feed Generated for PyMOTW Sun Mar 18 16:20:44 2018 https://pymotw.com/ episode-one.mp3 episode-two.mp3
Метаданные прогресса
В предыдущем примере имя файла и номер обрабатываемой строки не имели значения. Эта информация может понадобиться другим инструментам, таким как поиск, похожий на grep. fileinput
включает функции для доступа ко всем метаданным о текущей строке ( filename ()
, filelineno ()
и lneno ()
).
fileinput_grep.py
import fileinput import re import sys pattern re.compile(sys.argv[1]) for line in fileinput.input(sys.argv[2:]): if pattern.search(line): if fileinput.isstdin(): fmt '{lineno}:{line}' else: fmt '{filename}:{lineno}:{line}' print(fmt.format(filenamefileinput.filename(), linenofileinput.filelineno(), lineline.rstrip()))
Для поиска вхождений строки "fileinput"
в источнике для этих примеров можно использовать базовый цикл сопоставления с образцом.
$ python3 fileinput_grep.py fileinput *.py fileinput_change_subnet.py:10:import fileinput fileinput_change_subnet.py:17:for line in fileinput.input(files, fileinput_change_subnet_noisy.py:10:import fileinput fileinput_change_subnet_noisy.py:18:for line in fileinput.input( files, fileinput_change_subnet_noisy.py:19: if fileinput.isfirstline (): fileinput_change_subnet_noisy.py:21: fileinput.filena me())) fileinput_example.py:6:"""Example for fileinput module. fileinput_example.py:10:import fileinput fileinput_example.py:30:for line in fileinput.input(sys.argv[1:] ): fileinput_grep.py:10:import fileinput fileinput_grep.py:16:for line in fileinput.input(sys.argv[2:]): fileinput_grep.py:18: if fileinput.isstdin(): fileinput_grep.py:22: .filename(), fileinput_grep.py:23: ilelineno(),
Текст также можно читать со стандартного ввода.
$ cat *.py | python fileinput_grep.py fileinput 10:import fileinput 17:for line in fileinput.input(files, 29:import fileinput 37:for line in fileinput.input(files, 38: if fileinput.isfirstline(): 40: fileinput.filename())) 54:"""Example for fileinput module. 58:import fileinput 78:for line in fileinput.input(sys.argv[1:]): 101:import fileinput 107:for line in fileinput.input(sys.argv[2:]): 109: if fileinput.isstdin(): 113: 114:
Фильтрация на месте
Другой распространенной операцией обработки файлов является изменение содержимого файла, где оно находится, а не создание нового файла. Например, при изменении диапазона подсети может потребоваться обновление файла хостов Unix.
etc_hosts.txt до модификаций
## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost 10.16.177.128 hubert hubert.hellfly.net 10.16.177.132 cubert cubert.hellfly.net 10.16.177.136 zoidberg zoidberg.hellfly.net
Безопасный способ внести изменение автоматически – создать новый файл на основе введенных данных и затем заменить оригинал отредактированной копией. fileinput
поддерживает это автоматически с помощью параметра inplace
.
fileinput_change_subnet.py
import fileinput import sys from_base sys.argv[1] to_base sys.argv[2] files sys.argv[3:] for line in fileinput.input(files, inplaceTrue): line line.rstrip().replace(from_base, to_base) print(line)
Хотя в сценарии используется print ()
, вывод не производится, поскольку fileinput
перенаправляет стандартный вывод в перезаписываемый файл.
$ python3 fileinput_change_subnet.py 10.16 10.17 etc_hosts.txt
В обновленном файле изменились IP-адреса всех серверов в сети 10.16.0.0/16
.
etc_hosts.txt после модификаций
## # Host Database # # localhost is used to configure the loopback interface # when the system is booting. Do not change this entry. ## 127.0.0.1 localhost 255.255.255.255 broadcasthost ::1 localhost fe80::1%lo0 localhost 10.17.177.128 hubert hubert.hellfly.net 10.17.177.132 cubert cubert.hellfly.net 10.17.177.136 zoidberg zoidberg.hellfly.net
Перед началом обработки создается файл резервной копии с исходным именем плюс .bak
.
fileinput_change_subnet_noisy.py
import fileinput import glob import sys from_base sys.argv[1] to_base sys.argv[2] files sys.argv[3:] for line in fileinput.input(files, inplaceTrue): if fileinput.isfirstline(): sys.stderr.write('Started processing {}\n'.format( fileinput.filename())) sys.stderr.write('Directory contains: {}\n'.format( glob.glob('etc_hosts.txt*'))) line line.rstrip().replace(from_base, to_base) print(line) sys.stderr.write('Finished processing\n') sys.stderr.write('Directory contains: {}\n'.format( glob.glob('etc_hosts.txt*')))
Файл резервной копии удаляется при закрытии входа.
$ python3 fileinput_change_subnet_noisy.py 10.16. 10.17. etc_h\ osts.txt Started processing etc_hosts.txt Directory contains: ['etc_hosts.txt.bak', 'etc_hosts.txt'] Finished processing Directory contains: ['etc_hosts.txt']
Смотрите также
- стандартная библиотечная документация для ввода файлов
- m3utorss – скрипт для преобразования файлов m3u со списком MP3 в файл RSS, пригодный для использования в качестве канала подкастов.
xml.etree
– дополнительные сведения об использовании ElementTree для создания XML.