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']
另请参见
- 用于文件输入的标准库文档
- m3utorss–用于将列出 MP3 的 m3u 文件转换为适合用作播客 feed 的 RSS 文件的脚本。
xml.etree
-使用 ElementTree 生成 XML 的更多详细信息。
本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。