Jak sprawić, by programy w Pythonie zachowywały się jak odpowiednie narzędzia unixowe?

24

Mam kilka skryptów Pythona i pracuję nad ich napisaniem. Mam ten sam problem ze wszystkimi.

Nie jest dla mnie oczywiste, jak pisać programy, aby działały jak właściwe narzędzia uniksowe.

Ponieważ to

$ cat characters | progname

i to

$ progname characters

powinien generować ten sam wynik.

Najbliższą rzeczą, jaką mogłem znaleźć w Pythonie, była biblioteka fileinput. Niestety tak naprawdę nie widzę, jak przepisać moje skrypty w języku Python, z których wszystkie wyglądają tak:

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

Biblioteka fileinput przetwarza stdin, jeśli istnieje stdin, i przetwarza plik, jeśli istnieje plik. Ale iteruje się po pojedynczych liniach.

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

Naprawdę tego nie rozumiem. Myślę, że jeśli masz do czynienia z małymi plikami lub jeśli nie robisz wiele z plikami, może to wydawać się oczywiste. Ale dla moich celów jest to znacznie wolniejsze niż po prostu otwieranie całego pliku i wczytywanie go w ciąg znaków, jak powyżej.

Obecnie uruchamiam skrypt powyżej jak

$ pythonscript textfilename1 > textfilename2

Ale chcę być w stanie uruchomić go (i jego braci) w fajkach

$ grep pattern textfile1 | pythonscript | pythonscript | pythonscript > textfile2
ixtmixilix
źródło

Odpowiedzi:

9

Dlaczego nie tylko

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

for file in files:
    f = open(file)
    ...
Mikel
źródło
12
sys.stdinpowinien być używany zamiast tego, ponieważ jest bardziej przenośny niż zakodowana na stałe ścieżka do pliku.
Piotr Dobrogost
sys.stdinnależy zamiast tego użyć, jak mówi Piotr
smci
Ale sys.stdinjest plikiem i jest już otwarty i nie można go zamknąć. Niemożliwe jest obsługiwanie jak argument pliku bez przeskakiwania przez obręcze.
Alexis
@alexis Oczywiście, jeśli chcesz zamknąć flub użyć menedżera kontekstu, potrzebujesz czegoś bardziej złożonego. Zobacz moją nową odpowiedź jako alternatywę.
Mikel
12

Sprawdź, czy nazwa pliku jest podana jako argument, czy też odczytana z sys.stdin.

Coś takiego:

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

Jest podobny do odpowiedzi Mikela, tyle że korzysta z sysmodułu. Sądzę, że jeśli go tam mają, to musi być jakiś powód ...

rahmu
źródło
Co się stanie, jeśli w wierszu polecenia podano dwie nazwy plików?
Mikel
3
Och, absolutnie! Nie zadałem sobie trudu, aby to pokazać, ponieważ zostało to już pokazane w twojej odpowiedzi. W pewnym momencie musisz zaufać użytkownikowi, który zdecyduje, czego potrzebuje. Ale możesz edytować, jeśli uważasz, że to jest najlepsze. Chodzi mi o to tylko, aby wymienić "open(/dev/stdin")się sys.stdin.
rahmu
2
możesz chcieć sprawdzić if len(sys.argv)>1:zamiast w if sys.argv[1]:przeciwnym razie otrzymasz błąd poza zakresem indeksu
Yibo Yang
3

Moim preferowanym sposobem na to okazuje się ... (i pochodzi z ładnego małego linuksowego bloga 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()

Powodem, dla którego podobało mi się to najbardziej, jest to, jak mówi bloger, że po prostu wywołuje głupią wiadomość, jeśli przypadkowo zostanie wywołana bez udziału. Tak dobrze wpasowuje się we wszystkie moje istniejące skrypty Pythona, że ​​zmodyfikowałem je wszystkie, aby je uwzględnić.

ixtmixilix
źródło
3
Czasami chcesz wprowadzić dane interaktywnie z tty; sprawdzanie isattyi ratowanie nie jest zgodne z filozofią filtrów uniksowych.
musiphil
Oprócz isattybrodawki, obejmuje to użyteczny i ważny grunt, którego nie znaleziono w innych odpowiedziach, więc dostaje moją opinię.
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)
JJoao
źródło
Tak bym to napisał, gdyby /dev/stdinbyły niedostępne na wszystkich moich systemach.
Mikel
0

Korzystam z tego rozwiązania i działa jak urok. Właściwie używam w skrypcie calle unaccent, który zamienia małe litery i usuwa akcenty z danego ciągu

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

Chyba pierwszy raz, kiedy zobaczyłem, że to rozwiązanie jest tutaj .

SergioAraujo
źródło
0

Jeśli twój system nie ma /dev/stdinlub potrzebujesz bardziej ogólnego rozwiązania, możesz wypróbować coś bardziej skomplikowanego, na przykład:

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
źródło
Dlaczego przesuwasz wskaźnik pliku przy wyjściu? Kiepski pomysł. Jeśli dane wejściowe zostały przekierowane z pliku, następny program ponownie je przeczyta. (A jeśli stdin jest terminalem, poszukiwanie zwykle nic nie robi, prawda?) Po prostu zostaw to w spokoju.
Alexis
Tak, gotowe. Po prostu pomyślałem, że używanie tego słowa było urocze -. :)
Mikel