Wie bringe ich Python-Programme dazu, sich wie richtige Unix-Tools zu verhalten?

24

Ich habe ein paar Python-Skripte herumliegen, und ich arbeite daran, sie umzuschreiben. Ich habe mit allen das gleiche Problem.

Mir ist nicht klar, wie man die Programme so schreibt, dass sie sich wie richtige Unix-Tools verhalten.

Weil das

$ cat characters | progname

und das

$ progname characters

sollte die gleiche Ausgabe erzeugen.

Das Nächste, was ich in Python finden konnte, war die Dateieingabebibliothek. Leider sehe ich nicht wirklich, wie ich meine Python-Skripte umschreiben kann, die alle so aussehen:

#!/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

Die Dateieingabebibliothek verarbeitet stdin, wenn es eine stdin gibt, und verarbeitet eine Datei, wenn es eine Datei gibt. Es wird jedoch über einzelne Zeilen iteriert.

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

Das verstehe ich wirklich nicht. Ich denke, wenn Sie mit kleinen Dateien zu tun haben oder wenn Sie nicht viel mit den Dateien tun, scheint dies offensichtlich zu sein. Für meine Zwecke ist dies jedoch viel langsamer als das einfache Öffnen der gesamten Datei und das Einlesen in eine Zeichenfolge, wie oben.

Zur Zeit starte ich das obige Skript gerne

$ pythonscript textfilename1 > textfilename2

Aber ich möchte in der Lage sein, es (und seine Brüder) in Rohren zu betreiben

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

Antworten:

9

Warum nicht einfach

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

for file in files:
    f = open(file)
    ...
Mikel
quelle
12
sys.stdinsollte stattdessen verwendet werden, da es portabler ist als der fest codierte Pfad zur Datei.
Piotr Dobrogost
sys.stdinsollte stattdessen verwendet werden, wie Piotr sagt
smci
Aber sys.stdinist eine Datei, und es ist bereits geöffnet und darf nicht geschlossen werden. Es ist unmöglich, ein Argument wie eine Datei zu behandeln, ohne durch die Rahmen zu springen.
alexis
@alexis Sicher, wenn Sie schließen foder einen Kontextmanager verwenden möchten, benötigen Sie etwas Komplexeres. Siehe meine neue Antwort als Alternative.
Mikel
12

Überprüfen Sie, ob ein Dateiname als Argument angegeben oder aus diesem gelesen wurde sys.stdin.

Etwas wie das:

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

Es ist ähnlich wie die Antwort von Mikel, außer dass es das sysModul verwendet. Ich denke, wenn sie es da drin haben, muss es einen Grund haben ...

rahmu
quelle
Was ist, wenn zwei Dateinamen in der Befehlszeile angegeben werden?
Mikel
3
Oh, absolut! Ich habe mich nicht darum gekümmert, es zu zeigen, weil es bereits in Ihrer Antwort gezeigt wurde. Irgendwann muss man der Benutzerin vertrauen, um zu entscheiden, was sie braucht. Wenn Sie der Meinung sind, dass dies das Beste ist, können Sie es jederzeit bearbeiten. Mein Punkt ist nur zu ersetzen "open(/dev/stdin")durch sys.stdin.
Rahmu
2
Vielleicht möchten Sie überprüfen, if len(sys.argv)>1:anstatt if sys.argv[1]:sonst erhalten Sie einen Index außerhalb des Bereichs Fehler
Yibo Yang
3

Meine bevorzugte Vorgehensweise ist ... (und diese stammt aus einem netten kleinen Linux-Blog namens 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()

Der Grund, warum mir das am besten gefallen hat, ist, dass es, wie der Blogger sagt, nur eine dumme Nachricht ausgibt, wenn es versehentlich ohne Eingabe aufgerufen wird. Es fügt sich auch so gut in alle meine vorhandenen Python-Skripte ein, dass ich sie alle so modifiziert habe, dass sie es enthalten.

ixtmixilix
quelle
3
Manchmal möchten Sie die Eingabe interaktiv von einem tty aus eingeben. Überprüfung isattyund Rettung entsprechen nicht der Philosophie von Unix-Filtern.
Musiphil
Abgesehen von der isattyWarze deckt dies nützlichen und wichtigen Grund ab, der in den anderen Antworten nicht zu finden ist, so dass es meine Zustimmung erhält.
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)
Joao
quelle
So hätte ich es geschrieben, wenn /dev/stdines nicht auf allen meinen Systemen verfügbar gewesen wäre.
Mikel
0

Ich benutze diese Lösung und es funktioniert wie ein Zauber. Eigentlich verwende ich in einem Skript calle unaccent , das Akzente in einer bestimmten Zeichenfolge herabsetzt und entfernt

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

Ich denke, die beste Zeit, als ich diese Lösung sah, war hier .

SergioAraujo
quelle
0

Wenn Ihr System über keine /dev/stdinoder eine allgemeinere Lösung verfügt, können Sie etwas Komplizierteres ausprobieren:

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:
            ...
Mikel
quelle
Warum bewegen Sie den Dateizeiger beim Beenden? Schlechte Idee. Wenn die Eingabe aus einer Datei umgeleitet wurde, wird sie vom nächsten Programm erneut gelesen. (Und wenn stdin ein Terminal ist, macht seek normalerweise nichts, oder?) Lass es einfach in Ruhe.
Alexis
Ja, fertig. Ich fand es einfach süß, -mehrere Male zu verwenden. :)
Mikel