Scopo | Crea programmi filtro da riga di comando per elaborare righe da flussi di input |
Versione Python | 1.5.2 e superiore |
A partire dal 1 gennaio 2021 le versioni 2.x di Python non sono piu' supportate. Ti invito a consultare la corrispondente versione 3.x dell'articolo per il modulo fileinput
Il modulo fileinput è una struttura per creare programmi da riga di comando per elaborare dei file di testo in modo "filtrato".
Ad esempio l'applicazione m3utorss - scritta dall'autore dell'articolo originale Doug Hellmann, creata per il suo amico Patrick (n.d.t.) - per convertire alcune incisioni demo in un formato che possa essere trasmesso in podcast.
Gli input per il programma sono uno o più file m3u contenenti l'elenco dei file mp3 da distribuire. L'output è un singolo file XML, che assomiglia ad un feed RSS (l'ouput viene scritto allo stdout , per semplicità). Per elaborare l'input, occorre iterare attraverso l' elenco di nomi di file e:
Avrei potuto (Doug Hellmann - n.d.t.) scrivere la parte che gestisce i file manualmente. Non è così complicato, e con qualche test sono sicuro che avrei potuto anche gestire correttamente gli errori. Ma con il modulo fileinput non devo preoccuparmi di questo. Basta semplicemente scrivere qualcosa del tipo:
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 funzione
fileinput.input()
riceve come parametro una lista di nomi di file da esaminare. Se la lista è vuota, il modulo legge i dati dallo
standard input
. La funzione ritorna un
iteratore
che a sua volta restituisce le singole righe dal file che sta elaborando. Quindi, tutto quello che si deve fare è eseguire un ciclo per ogni riga, saltando quelle vuote ed i commenti, per trovare i riferimenti ai file mp3.
Ecco il listato completo del programma:
import fileinput
import sys
import time
from xml.etree.ElementTree import Element, SubElement, tostring
from xml.dom import minidom
# Impostazione dei nodi RSS e channel
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 = 'http://www.doughellmann.com/PyMOTW/'
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=" ")
ed il suo risultato:
$ python 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> Thu Mar 25 10:49:28 2010 </pubDate> <generator> http://www.doughellmann.com/PyMOTW/ </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>
Nell'esempio precedente, non interessa quale file o numero di riga si sta processando in fase di input. Per altri strumenti (ricerche sul tipo di grep, ad esempio) queste informazioni potrebbero interessare. Il modulo
fileinput
include le funzioni per accedere a quelle informazioni (
filename()
restituisce il nome del file attualmente in lettura,
filelineno()
restituisce il numero di righe lette nel file corrente,
lineno()
restituisce il numero totale di righe lette, ecc.).
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:<20}:{lineno}:{line}'
print fmt.format(filename=fileinput.filename(),
lineno=fileinput.filelineno(),
line=line.rstrip())
Si può usare questo basico ciclo per la corrispondenza di stringhe per trovare le occorrenze di "fileinput" nel sorgente di questi esempi.
$ python 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:20: sys.stderr.write('Started processing %s\n' % fileinput.filename()) 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.filelineno(),
Si può anche passare l'input attraverso stdin
$ 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(): 39: sys.stderr.write('Started processing %s\n' % fileinput.filename()) 51:"""Example for fileinput module. 55:import fileinput 75:for line in fileinput.input(sys.argv[1:]): 96:import fileinput 102:for line in fileinput.input(sys.argv[2:]): 104: if fileinput.isstdin(): 108: print fmt.format(filename=fileinput.filename(), 109: lineno=fileinput.filelineno(),
Un'altra comune operazione di elaborazione file è la modifica del contenuto. Ad esempio un file Unix hosts potrebbe avere bisogno di essere aggiornato se viene modificato l'intervallo di una subnet.
## # 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 172.16.177.128 hubert hubert.hellfly.net 172.16.177.132 cubert cubert.hellfly.net 172.16.177.136 zoidberg zoidberg.hellfly.net
Un modo sicuro per eseguire le modifiche automaticamente è creare un nuovo file basato sull'input, quindi rimpiazzare l'originale con la copia modificata. fileinput supporta questo automaticamente usando l'opzione 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
$ python fileinput_change_subnet.py 172.16.177 172.16.178 etc_hosts.txt
Sebbene lo script usi
print
, non viene prodotto alcun output verso
stdout
perchè
fileinput
mappa
stdout
al file che si sta sovrascrivendo.
## # 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 172.16.178.128 hubert hubert.hellfly.net 172.16.178.132 cubert cubert.hellfly.net 172.16.178.136 zoidberg zoidberg.hellfly.net
Prima che inizi l'elaborazione, un file di backup viene creato usando il nome originale più
.bak
. Il file di backup viene rimosso quando viene chiuso l'input.
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('Iniziata elaborazione %s\n' % fileinput.filename())
sys.stderr.write('Contenuto della directory: %s\n' % glob.glob('etc_hosts.txt*'))
line = line.rstrip().replace(from_base, to_base)
print line
sys.stderr.write('Finita elaborazione\n')
sys.stderr.write('Contenuto della directory: %s\n' % glob.glob('etc_hosts.txt*'))
$ python fileinput_change_subnet_noisy.py 172.16.177 172.16.178 etc_hosts.txt Iniziata elaborazione etc_hosts.txt Contenuto della directory: ['etc_hosts.txt', 'etc_hosts.txt.bak'] Finita elaborazione Contenuto della directory: ['etc_hosts.txt']
Vedere anche: