Рубрики
Без рубрики

fileinput – каркас фильтра командной строки

Автор оригинала: 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.m3u



  
    Sample 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']

Смотрите также