Scopo | Crea pipeline di comandi della shell Unix ripetibili |
Versione Python | 1.4 |
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 le versioni 3.x di Python
Il modulo pipes implementa una classe per gestire pipeline di comandi Unix arbitrariamente complesse, Gli input ed output dei comandi possono essere concatenati come con l'operatore | della shell; anche se i singoli comandi devono scrivere su o leggere da file invece che da stdin/stdout.
Un esempio molto semplice, passare lo standard input attraverso una pipe e ricevere i risultati in un file potrebbe essere il seguente:
import pipes
import tempfile
# Si predispone una pipeline molto semplice usando stdio
p = pipes.Template()
p.append('cat -', '--')
p.debug(True)
# Si passa del testo attraverso la pipeline,
# salvando l'output in un file temporaneo
t = tempfile.NamedTemporaryFile(mode='r')
f = p.open(t.name, 'w')
try:
f.write('Porzione di testo')
finally:
f.close()
# Ritorno ad inizio file e lettura del testo scritto
# nel file temporaneo
t.seek(0)
print t.read()
t.close()
Viene creato l'oggetto
pipelines.Template
, quindi viene aggiunto un singolo comando,
cat -
. Il comando legge lo standard input e lo scrive allo standard output, senza modifiche. Il secondo parametro per
append()
codifica le sorgenti di input ed output per il comando in due caratteri (input, quindi output). L'uso di
-
significa che il comando usa lo standard I/O. L'uso di
f
significa che il comando deve leggere da un file (come potrebbe essere nel caso di una pipeline per l'elaborazione di un'immagine).
Il metodo
debug()
attiva/disattiva l'output di debug. Quando il debug è abilitato, i comandi che sono eseguiti vengono stampati ed alla shell viene passato
set -x
in modo che venga eseguito in modalità dettagliata ("verbose").
Dopo che è stata impostata la pipeline, viene creato un
NamedTemporaryFile
per fornire alla pipeline un file su cui scrivere il suo output. Occorre sempre specificare un file come parametro per
open()
, sia in lettura che scrittura.
$ python pipes_simple_write.py cat - >/tmp/tmpqdcQW4 + cat - Porzione di testo
Leggere da una pipeline funziona praticamente allo stesso modo, con poche modifiche ai parametri. Per questo esempio, occorre impostare il contenuto del file di input prima di aprire la pipeline. Quindi si può passare quel nome di file come input di
open()
.
import pipes
import tempfile
# Si predispone una pipeline molto semplice usando stdio
p = pipes.Template()
p.append('cat -', '--')
p.debug(True)
# Si imposta un file di input
t = tempfile.NamedTemporaryFile(mode='w')
t.write('Porzione di testo')
t.flush()
# Si passa del testo attraverso la pipeline,
# salvando l'output in un file temporaneo
f = p.open(t.name, 'r')
try:
contents = f.read()
finally:
f.close()
print contents
Si può leggere il risultato dalla pipeline direttamente
python pipes_simple_read.py cat - </tmp/tmps_ABth + cat - Porzione di testo
Alcuni comandi devono lavorare sui file in un filesystem invece che con flussi di input. Comandi che elaborano un grande numero di dati potrebbero fornire prestazioni migliori in questo modo, visto che non si bloccheranno sul prossimo comando che sta leggendo il loro output. Qualsiasi cosa che funzioni su dati non basati su flussi deve avere questa capacità (es. i database od altri strumenti di manipolazione di file binari). Per supportare questa modalità operativa,
append()
consente di specificare un
tipo
di
f
, ed il codice della pipeline creerà i necessari file temporanei. I nomi di file sono passati alla shell come
e
, quindi questi nomi di variabili devono comparire nella propria stringa di comandi.
import pipes
import tempfile
p = pipes.Template()
p.append('cat $IN > $OUT', 'ff')
p.debug(True)
t = tempfile.NamedTemporaryFile('r')
f = p.open(t.name, 'w')
try:
f.write('Porzione di testo')
finally:
f.close()
t.seek(0)
print t.read()
t.close()
Così si vede, sono stati creati parecchi file temporanei intermedi per mantenere l'input e l'output di ogni passo.
$ python pipes_file_kind.py trap 'rm -f /tmp/tmpDWnC9N; exit' 1 2 3 13 14 15 cat >/tmp/tmpDWnC9N IN=/tmp/tmpDWnC9N; OUT=/tmp/tmp79Dd6d; cat > rm -f /tmp/tmpDWnC9N + trap rm -f /tmp/tmpDWnC9N; exit 1 2 3 13 14 15 + cat + IN=/tmp/tmpDWnC9N + OUT=/tmp/tmp79Dd6d + cat /tmp/tmpDWnC9N + rm -f /tmp/tmpDWnC9N Porzione di testo
I valori kind di input ed output possono essere mescolati, in modo che diversi passi della pipeline possano usare a piacimento i file o lo standard I/O.
import pipes
import tempfile
p = pipes.Template()
p.append('cat $IN', 'f-')
p.append('cat - > $OUT', '-f')
p.debug(True)
t = tempfile.NamedTemporaryFile('r')
f = p.open(t.name, 'w')
try:
f.write('Porzione di testo')
finally:
f.close()
t.seek(0)
print t.read()
t.close()
Le istruzioni trap che si vedono nell'output assicurano che i file temporanei creati dalla pipeline siano puliti anche se una attività nel mezzo fallisce oppure se la shell viene chiusa.
$ python pipes_mixed_kinds.py trap 'rm -f /tmp/tmpN_eiEw; exit' 1 2 3 13 14 15 cat >/tmp/tmpN_eiEw IN=/tmp/tmpN_eiEw; cat | { OUT=/tmp/tmp1BUupk; cat - > ; } rm -f /tmp/tmpN_eiEw + trap rm -f /tmp/tmpN_eiEw; exit 1 2 3 13 14 15 + cat + IN=/tmp/tmpN_eiEw + cat /tmp/tmpN_eiEw + OUT=/tmp/tmp1BUupk + cat - + rm -f /tmp/tmpN_eiEw Porzione di testo
Tutti gli esempi fino a qui sono stati piuttosto rozzi. Sono stati concepiti per illustrare l'uso di
pipes.Template()
senza dovere dipendere da una profonda conoscenza della programmazione della shell in generale. Questo esempio è più complesso, e mostra come parecchi comandi possano essere combinati per manipolare i dati prima di portarli all'interno di Python.
Lo script virtualwrapper comprende una funzione di shell per elencare tutti gli ambienti virtuali che si sono creati. La funzione viene usata per il completamento con il tasto "tab" e può essere chiamata direttamente per elencare gli ambienti, nel caso ci si dimentichi un nome. Il cuore di quella funzione è una piccola pipeline che cerca in le directory che potrebbero sembrare degli ambienti virtuali (es. hanno uno script activate ). Quella pipeline é:
(cd ""; for f in */bin/activate; do echo ; done) \ | sed 's|^\./||' \ | sed 's|/bin/activate||' \ | sort
Implementata usando pipes la pipeline diventa:
import pipes
import pprint
import tempfile
p = pipes.Template()
p.append('cd "$WORKON_HOME"; for f in */bin/activate; do echo $f; done', '--')
p.append(r"sed 's|^\./||'", '--')
p.append("sed 's|/bin/activate||'", '--')
p.append('sort', '--')
t = tempfile.NamedTemporaryFile('r')
f = p.open(t.name, 'r')
try:
sandboxes = [ l.strip() for l in f.readlines() ]
finally:
f.close()
print 'SANDBOXES:'
pprint.pprint(sandboxes)
Visto che ogni nome di sandbox viene scritto in una riga a parte, l'analisi dell'output è semplice:
$ python pipes_multistep.py SANDBOXES: ['CastSampler', 'bbtest', 'bill', 'blogbackup', 'cursive.tools', 'docket', 'docket-new', 'doughellmann', 'email_recovery', 'ical2org', 'mytweets', 'nose', 'odtwritertest', 'paramiko', 'personal', 'psf-sprints', 'psfboard', 'pulse', 'pyatl', 'pymag', 'pymotw', 'pymotw-ja', 'pymotw26', 'python-dev', 'racemi', 'racemi_status', 'reporting_server', 'rst2marsedit', 'sphinx-contrib', 'sphinx-contrib.old', 'virtualenvwrapper']
Se l'input della propria pipeline esiste già in un file su disco, non c'è necessità di leggerlo dentro Python semplicemente per passarlo alla pipeline. Si può usare il metodo
copy()
per passare il file direttamente attraverso la pipeline e creare un file di output per la lettura.
import pipes
import tempfile
p = pipes.Template()
p.debug(True)
p.append('grep -n tortor $IN', 'f-')
t = tempfile.NamedTemporaryFile('r')
p.copy('lorem.txt', t.name)
t.seek(0)
print t.read()
t.close()
$ python pipes_copy.py + IN=lorem.txt + grep -n tortor lorem.txt IN=lorem.txt; grep -n tortor >/var/folders/9R/9R1t+tR02Raxzk+F71Q50U+++Uw/-Tmp-/tmplLJu9t 3:elementum elit tortor eu quam. Duis tincidunt nisi ut ante. Nulla 6:lacus. Praesent placerat tortor sed nisl. Nunc blandit diam egestas 11:eget velit auctor tortor blandit sollicitudin. Suspendisse imperdiet
Una volta che si ha un template di pipeline, si potrebbe volerlo usare per diverse volte o creare varianti senza ricostruire l'intero oggetto. Il metodo
clone()
rende semplici entrambe le operazioni. Questo esempio costruisce una semplice pipeline per contare le parole, quindi antepone i comandi ad un paio di cloni per farli cercare parole diverse.
import pipes
import tempfile
count_word_substring = pipes.Template()
#count_word_substring.debug(True)
count_word_substring.append('grep -f - /usr/share/dict/words', '--')
count_word_substring.append('wc -l', '--')
count_py = count_word_substring.clone()
count_py.prepend('echo "py"', '--')
f = count_py.open('/dev/null', 'r')
try:
print ' "py": %5s' % f.read().strip()
finally:
f.close()
count_perl = count_word_substring.clone()
count_perl.prepend('echo "perl"', '--')
f = count_perl.open('/dev/null', 'r')
try:
print '"perl": %5s' % f.read().strip()
finally:
f.close()
Anteponendo un comando personalizzato per ogni clone, si possono creare pipeline separate che eseguono la stessa funzione base con piccole variazioni.
$ python pipes_clone.py "py": 1379 "perl": 71
Vedere anche: