2010-02-15 7 views
17

Ho uno script CLI e voglio che legga i dati da un file. Dovrebbe essere in grado di leggere in due modi:Come leggere da stdin o da un file se nessun dato viene reindirizzato in Python?

  • cat data.txt | ./my_script.py
  • ./my_script.py data.txt

po '-a come grep, per esempio.

quello che so:

  • sys.argv e optparse Permettetemi di leggere qualsiasi args e opzioni facilmente.
  • sys.stdin Permettetemi di leggere i dati di sottofondo
  • fileinput rendere il processo completamente automatico

Sfortunatamente:

  • utilizzando fileinput utilizza standard input e gli eventuali argomenti come input. Quindi non posso usare opzioni che non sono nomi di file mentre cerca di aprirli.
  • sys.stdin.readlines() funziona bene, ma se non lo faccio tubo di tutti i dati, si blocca fino a quando io entro Ctrl + D
  • non so come implementare "se nulla in stdin, lette da un file in args "perché stdin è sempre True in un contesto booleano.

Mi piacerebbe un modo portatile per farlo, se possibile.

+0

Grazie a tutti, ho imparato molto oggi. –

risposta

10

processo tuoi argomenti non nomi di file tuttavia vuoi, così si finisce con una serie di argomenti che non siano opzioni, quindi passare questo array come parametro per fileinput.input():

import fileinput 
for line in fileinput.input(remaining_args): 
    process(line) 
+0

Mi piace, sembra molto efficace. C'è qualcosa di male che mi manca? –

+0

Credo che questo fornirà un comportamento simile ad altri comandi Unix. In un altro commento hai detto che sei un po 'preoccupato dell'effetto hang-until-input quando non vengono forniti argomenti; a meno che non si prenda provvedimenti per notare quando non vengono passati argomenti, questo avverrà comunque. Non c'è motivo per cui non dovresti prendere questo caso, però, dato che passa '-' come un parametro leggerà ancora da stdin. –

+0

Userò una combinazione del consiglio di Ignocio e della soluzione di Andrew. –

3

Non esiste un modo affidabile per rilevare se sys.stdin è collegato a qualcosa, né è opportuno farlo (ad esempio, l'utente desidera incollare i dati in) Rileva la presenza di un nome di file come argomento e usa stdin se non viene trovato alcun nome.

+0

Grazie a tutti e due. Dato che mi manca la conoscenza necessaria per scegliere tra la tua risposta e quella di gnibbler, quali sono gli svantaggi di ciascuno? Sembrano entrambi validi. –

+0

Oh, ho appena provato questo approccio ma il loro è un problema: se non ci sono file e non lo stdin, finisco per leggere stdin e si blocca. Come posso scrivere un messaggio di errore all'utente per dirgli di fornire i dati? –

+0

Si noti bene che l'hang-until-EOF è inevitabile qui se qualcuno lo esegue senza un nome file - questo è un comportamento standard nel mondo Unix. vedi anche grep, cat, ecc. Se questo non è accettabile, l'unico modo per evitarlo è usare un'altra convenzione tipica in cui fornire un nome di file '-' significa 'leggere da stdin' (o scrivere su stdout) . –

8

per UNIX/Linux è possibile rilevare se i dati vengono convogliata in, cercando in os.isatty(0)

$ date | python -c "import os;print os.isatty(0)" 
False 
$ python -c "import os;print os.isatty(0)" 
True 

io non sono sicuro che ci sia un equivalente per Windows.

modificare Ok, ho provato con python2.6 su Windows XP

C:\Python26>echo "hello" | python.exe -c "import os;print os.isatty(0)" 
False 

C:\Python26> python.exe -c "import os;print os.isatty(0)" 
True 

Quindi forse non tutto senza speranza per le finestre

+1

Grazie a tutti e due. Dato che mi manca la conoscenza necessaria per scegliere tra la tua risposta e quella di Ignocio, quali sono gli svantaggi di ciascuno? Sembrano entrambi validi. –

+0

@ e-satis: cosa succede se nessun nome file viene passato come argomento? Quando puoi rispondere a questa domanda, saprai cosa devi fare. –

+0

Per ora, sembra funzionare così l'ho accettato. Grazie ancora. Sono ancora interessato a conoscere i problemi con quello che sto facendo. Cos'è un tty e perché funziona? Quando no? –

20

Argparse permette che questo essere fatto in un abbastanza facile modo, e dovresti davvero usarlo invece di optparse a meno che tu non abbia problemi di compatibilità.

Il codice dovrebbe andare qualcosa come questo:

import argparse 
parser = argparse.ArgumentParser() 
parser.add_argument('--input', type = argparse.FileType('r'), default = '-') 

Ora avete un parser in grado di analizzare i tuoi argomenti della riga di comando, utilizzare un file se vede uno, o utilizzare lo standard input se non lo fa.

+0

Grazie anche a te. Sto imparando molto oggi! Ad ogni modo, non credi che ci sia un modo per farlo con la lib standard? Se no, sto bene con argparse. Ma optparse esiste ... –

+0

argparse è piuttosto piccolo, è puro codice python ed è anche più bello da usare di optparse. Mentre di solito non vorrei aggiungere una nuova dipendenza a un progetto solo per leggere le opzioni della riga di comando, i tre fattori sopra riportati hanno reso argparse più che utile nella mia esperienza. – mavnn

+0

Questo può essere fatto nello stesso volume di codice in 'optparse'. –

2

È possibile utilizzare questa funzione per rilevare se l'input proviene da una pipeline o meno.

sys.stdin.isatty() 

Restituisce false se l'input proviene dalla pipeline o vero altrimenti.

+0

Il comportamento del tuo programma non dovrebbe dipendere in modo significativo dal fatto che 'stdin' sia connesso a un tty. Se non viene fornito alcun nome di file, è sufficiente leggere da 'stdin', che si tratti di una tty o di una pipe. – musiphil

+0

Questo mi ha davvero aiutato. Avevo bisogno di sapere se i miei dati erano stati inviati in pipe o in tty per un particolare progetto e questa era l'unica buona soluzione che potevo usare. – Blairg23

4

Sono un noob, quindi questa potrebbe non essere una buona risposta, ma sto cercando di fare la stessa cosa (consenti uno o più file sulla riga di comando, altrimenti STDIN di default).

Il combo finale ho messo insieme:

parser = argparse.ArgumentParser() 
parser.add_argument("infiles", nargs="*") 
args = parser.parse_args() 

for line in fileinput.input(args.infiles): 
    process(line) 

Questo mi sembra l'unico modo per ottenere tutto il comportamento desiderato in un unico pacchetto elegante, senza la necessità di args nome. Proprio come i comandi Unix sono utilizzati come tali:

cat file1 file2 
wc -l < file1 
Non

:

cat --file file1 --file file2 

Apprezzerei un feedback/conferma da Pythonisti idiomatiche veterani per assicurarsi che ho la migliore risposta. Non ho visto questa soluzione completa menzionata altrove, solo frammenti.

+1

Un'altra cosa: se non vuoi dipendere dal prossimo ragazzo sapendo già che fileinput.input() magicamente ha come valore predefinito stdin quando ottiene una lista vuota, puoi aggiungere ", default =" - "" al chiama a add_argument(). Non cambia nulla, ma rende la logica perfettamente esplicita. – odigity