Come faccio a far sì che i programmi Python si comportino come strumenti unix adeguati?


24

Ho alcuni script Python in giro e sto lavorando per riscriverli. Ho lo stesso problema con tutti loro.

Non è ovvio per me come scrivere i programmi in modo che si comportino come strumenti unix adeguati.

Perchè questo

$ cat characters | progname

e questo

$ progname characters

dovrebbe produrre lo stesso output.

La cosa più vicina a Python che ho trovato è stata la libreria fileinput. Sfortunatamente, non vedo davvero come riscrivere i miei script Python, che sembrano tutti così:

#!/usr/bin/env python 
# coding=UTF-8

import sys, re

for file in sys.argv[1:]:
    f = open(file)
    fs = f.read()
    regexnl = re.compile('[^\s\w.,?!:;-]')
    rstuff = regexnl.sub('', fs)
    f.close()
    print rstuff

La libreria fileinput elabora stdin se esiste uno stdin ed elabora un file se esiste un file. Ma scorre su singole righe.

import fileinput
for line in fileinput.input():
    process(line)

Davvero non capisco. Immagino che se hai a che fare con file di piccole dimensioni o se non stai facendo molto con i file, questo può sembrare ovvio. Ma, per i miei scopi, questo rende molto più lento che semplicemente aprire l'intero file e leggerlo in una stringa, come sopra.

Attualmente eseguo lo script sopra come

$ pythonscript textfilename1 > textfilename2

Ma voglio essere in grado di eseguirlo (e i suoi fratelli) in pipe, come

$ grep pattern textfile1 | pythonscript | pythonscript | pythonscript > textfile2

Risposte:


9

Perché non solo

files = sys.argv[1:]
if not files:
    files = ["/dev/stdin"]

for file in files:
    f = open(file)
    ...

12
sys.stdindovrebbe essere usato invece in quanto è più portabile del percorso del file hardcoded.
Piotr Dobrogost,

sys.stdindovrebbe essere usato invece, come dice Piotr
smci

Ma sys.stdinè un file ed è già aperto e non deve essere chiuso. Impossibile gestire proprio come un argomento di file senza saltare attraverso i cerchi.
alexis

@alexis Certo, se vuoi chiudere f, o vuoi usare un gestore di contesto, hai bisogno di qualcosa di più complesso. Vedi la mia nuova risposta come alternativa.
Mikel

12

Controlla se un nome di file è dato come argomento, oppure leggi da sys.stdin.

Qualcosa come questo:

if sys.argv[1]:
   f = open(sys.argv[1])
else:
   f = sys.stdin 

È simile alla risposta di Mikel tranne per il fatto che utilizza il sysmodulo. Immagino che se ce l'hanno dentro deve essere per un motivo ...


Cosa succede se nella riga di comando vengono specificati due nomi di file?
Mikel,

3
Oh assolutamente! Non mi sono preoccupato di mostrarlo perché era già mostrato nella tua risposta. Ad un certo punto devi fidarti dell'utente per decidere di cosa ha bisogno. Sentiti libero di modificare se ritieni che sia la cosa migliore. Il mio punto è solo quello di sostituire "open(/dev/stdin")con sys.stdin.
Rahmu,

2
potresti voler controllare if len(sys.argv)>1:invece di if sys.argv[1]:ottenere un errore fuori indice
Yibo Yang

3

Il mio modo preferito di farlo risulta essere ... (e questo è tratto da un bel blog di Linux chiamato Harbinger's Hollow )

#!/usr/bin/env python

import argparse, sys

parser = argparse.ArgumentParser()
parser.add_argument('filename', nargs='?')
args = parser.parse_args()
if args.filename:
    string = open(args.filename).read()
elif not sys.stdin.isatty():
    string = sys.stdin.read()
else:
    parser.print_help()

Il motivo per cui mi è piaciuto di più è che, come dice il blogger, emette un messaggio sciocco se chiamato accidentalmente senza input. Si inserisce anche così bene in tutti i miei script Python esistenti che li ho modificati tutti per includerlo.


3
A volte si desidera inserire l'input in modo interattivo da un tty; il controllo isattye il salvataggio non sono conformi alla filosofia dei filtri Unix.
musiphil,

A parte la isattyverruca, questo copre un terreno utile e importante che non si trova nelle altre risposte, quindi ottiene il mio voto.
Tripleee,

3
files=sys.argv[1:]

for f in files or [sys.stdin]:
   if isinstance(f, file):
      txt = f.read()
   else:
      txt = open(f).read()

   process(txt)

Ecco come l'avrei scritto, se /dev/stdinnon fosse disponibile su tutti i miei sistemi.
Mikel,

0

Sto usando questa soluzione e funziona come un fascino. In realtà sto usando in un calle script unaccent che minuscolo e rimuove gli accenti da una determinata stringa

argument = sys.argv[1:] if len(sys.argv) > 1 else sys.stdin.read()

Immagino che il momento più importante in cui ho visto questa soluzione fosse qui .


0

Se il tuo sistema non ha /dev/stdin, o vuoi una soluzione più generale, potresti provare qualcosa di più complicato come:

class Stdin(object):
    def __getattr__(self, attr):
        return getattr(sys.stdin, attr)

    def __enter__(self):
        return self

def myopen(path):
    if path == "-":
        return Stdin()
    return open(path)

for n in sys.argv[1:] or ["-"]:
    with myopen(n) as f:
            ...

Perché si sposta il puntatore del file all'uscita? Cattiva idea. Se l'input è stato reindirizzato da un file, il programma successivo lo leggerà di nuovo. (E se lo stdin è un terminale, la ricerca di solito non fa nulla, giusto?) Lascialo da solo.
alexis

Sì, fatto. Ho solo pensato che fosse carino da usare -più volte. :)
Mikel
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.