15.10. fileinput — 命令行过滤器框架

未匹配的标注

目的:创建命令行过滤程序来处理输入流中的行。

fileinput模块是一个框架,用于创建用于将文本文件作为过滤器处理的命令行程序。

将M3U文件转换为RSS

过滤器的一个示例是 m3utorss,该程序可将一组MP3文件转换为可以作为播客共享的RSS feed。该程序的输入是一个或多个列出要分发的 MP3 文件的 m3u 文件。输出是打印到控制台的 RSS feed。要处理输入,程序需要遍历文件名列表和

  • 打开每个文件。
  • 读取文件的每一行。
  • 确定该行是否指向mp3文件。
  • 如果有,请将新项目添加到RSS feed。
  • 打印输出。

所有这些文件处理都可能是手工编码的。它并不那么复杂,并且通过一些测试,甚至错误处理也将是正确的。但是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 文件的引用。

Here is the complete program.

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会使用 RSS 格式生成 XML 数据。

$ python3 fileinput_example.py sample_data.m3u

<?xml version="1.0" ?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Sample podcast feed</title>
    <description>Generated for PyMOTW</description>
    <pubDate>Sun Mar 18 16:20:44 2018</pubDate>
    <generator>https://pymotw.com/</generator>
  </channel>
  <item>
    <title>episode-one.mp3</title>
    <enclosure type="audio/mpeg" url="episode-one.mp3"/>
  </item>
  <item>
    <title>episode-two.mp3</title>
    <enclosure type="audio/mpeg" url="episode-two.mp3"/>
  </item>
</rss>

进度元数据

在前面的示例中,正在处理的文件名和行号并不重要。其他工具(例如类似grep的搜索)可能需要该信息。 fileinput包括用于访问有关当前行的所有元数据的功能(filename()filelineno()lineno() )。

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(filename=fileinput.filename(),
                         lineno=fileinput.filelineno(),
                         line=line.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,
 inplace=True):
fileinput_change_subnet_noisy.py:10:import fileinput
fileinput_change_subnet_noisy.py:18:for line in fileinput.input(
files, inplace=True):
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:        print(fmt.format(filename=fileinput
.filename(),
fileinput_grep.py:23:                         lineno=fileinput.f
ilelineno(),

文本也可以从标准输入中读取。

$ cat *.py | python fileinput_grep.py fileinput

10:import fileinput
17:for line in fileinput.input(files, inplace=True):
29:import fileinput
37:for line in fileinput.input(files, inplace=True):
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:        print(fmt.format(filename=fileinput.filename(),
114:                         lineno=fileinput.filelineno(),

就地过滤

另一种常见的文件处理操作是修改文件所在的位置,而不是创建新文件。例如,如果子网范围更改,则可能需要更新 Unix 主机文件。

etc_hosts.txt before modifications

##
# 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, inplace=True):
    line = line.rstrip().replace(from_base, to_base)
    print(line)

尽管脚本使用print(),但由于fileinput将标准输出重定向到要覆盖的文件,因此不会产生任何输出。

$ python3 fileinput_change_subnet.py 10.16 10.17 etc_hosts.txt

更新的文件具有10.16.0.0/16网络上所有服务器的 IP 地址已更改。

修改后的 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, inplace=True):
    if fileinput.isfirstline():
        sys.stderr.write('Started processing {}.'.format(
            fileinput.filename()))
        sys.stderr.write('Directory contains: {}.'.format(
            glob.glob('etc_hosts.txt*')))
    line = line.rstrip().replace(from_base, to_base)
    print(line)

sys.stderr.write('Finished processing.')
sys.stderr.write('Directory contains: {}.'.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']

另请参见

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。
上一篇 下一篇
Summer
贡献者:2
讨论数量: 0
发起讨论 只看当前版本


暂无话题~