fileinput — Marco de filtro de línea de comandos¶
Propósito: | Crea programas de filtro de línea de comandos para procesar líneas desde flujos de entrada. |
---|
El módulo fileinput
es un marco para crear programas de línea de comandos
para procesar archivos de texto como filtro.
Conversión de archivos M3U a RSS¶
Un ejemplo de filtro es m3utorss, un programa para convertir un conjunto de archivos MP3 en una fuente RSS que se puede compartir como un podcast. Las entradas al programa son uno o más archivos m3u que enumeran los archivos MP3 que se distribuirán. La salida es una fuente RSS impresa en la consola. Para procesar la entrada, el programa necesita iterar sobre la lista de nombres de archivo y
- Abrir cada archivo.
- Leer cada línea del archivo.
- Averiguar si la línea se refiere a un archivo mp3.
- Si lo hace, agregar un nuevo elemento a la fuente RSS.
- Imprimir la salida.
Todo este manejo de archivos podría haber sido codificado a mano. No es tan
complicado y, con algunas pruebas, incluso el manejo de errores sería correcto.
Pero fileinput
maneja todos los detalles, por lo que el programa se
simplifica.
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})
La función input()
toma como argumento una lista de nombres de archivos
para examinar. Si la lista está vacía, el módulo lee datos de la entrada
estándar. La función devuelve un iterador que produce líneas individuales a
partir de los archivos de texto que se procesan. La persona que llama solo
necesita recorrer cada línea, omitiendo espacios en blanco y comentarios, para
encontrar las referencias a los archivos MP3.
Aquí está el programa completo.
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=" "))
Este archivo de entrada de muestra contiene los nombres de varios archivos MP3.
# This is a sample m3u file
episode-one.mp3
episode-two.mp3
Ejecutar fileinput_example.py
con la entrada de muestra produce datos XML
utilizando el formato RSS.
$ 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>
Metadatos de progreso¶
En el ejemplo anterior, el nombre de archivo y el número de línea que se
procesaba no eran importantes. Otras herramientas, como la búsqueda tipo grep,
pueden necesitar esa información. fileinput
incluye funciones para acceder
a todos los metadatos sobre la línea actual (filename()
, filelineno()
y
lineno()
).
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()))
Se puede usar un bucle básico de coincidencia de patrones para encontrar las
ocurrencias de la cadena "fileinput"
en la fuente de estos ejemplos.
$ 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(),
El texto también se puede leer desde la entrada estándar.
$ 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(),
Filtrado in situ¶
Otra operación común de procesamiento de archivos es modificar el contenido de un archivo donde está, en lugar de crear un nuevo archivo. Por ejemplo, un archivo de hosts Unix podría necesitar actualizarse si cambia un rango de subred.
##
# 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
La forma segura de realizar el cambio automáticamente es crear un nuevo archivo
basado en la entrada y luego reemplazar el original con la copia editada.
fileinput
admite esto automáticamente usando la opción inplace
.
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)
Aunque la secuencia de comandos usa print()
, no se produce ninguna salida
porque fileinput
redirige la salida estándar al archivo que se sobrescribe.
$ python3 fileinput_change_subnet.py 10.16 10.17 etc_hosts.txt
El archivo actualizado tiene las direcciones IP modificadas de todos los
servidores en la red 10.16.0.0/16
.
##
# 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
Antes de comenzar el procesamiento, se crea un archivo de copia de seguridad
con el nombre original más .bak
.
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 {}\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*')))
El archivo de copia de seguridad se elimina cuando se cierra la entrada.
$ 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']
Ver también
- Documentación de la biblioteca estándar para fileinput
- m3utorss – Secuencia de comandos para convertir archivos m3u que enumeran MP3 a un archivo RSS adecuado para su uso como fuente de podcast.
xml.etree
– Más detalles sobre el uso de ElementTree para producir XML.