Bagaimana cara membuat program python berperilaku seperti alat unix yang tepat?

24

Saya memiliki beberapa skrip Python yang bertebaran, dan saya sedang mengerjakan penulisan ulang. Saya memiliki masalah yang sama dengan mereka semua.

Tidak jelas bagi saya bagaimana menulis program sehingga mereka berperilaku seperti alat unix yang tepat.

Karena ini

$ cat characters | progname

dan ini

$ progname characters

harus menghasilkan output yang sama.

Hal terdekat yang dapat saya temukan dengan Python adalah perpustakaan fileinput. Sayangnya, saya tidak benar-benar melihat cara menulis ulang skrip Python saya, yang semuanya terlihat seperti ini:

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

Perpustakaan fileinput memproses stdin jika ada stdin, dan memproses file jika ada file. Tapi iterates lebih dari satu baris.

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

Saya benar-benar tidak mengerti. Saya kira jika Anda berurusan dengan file kecil, atau jika Anda tidak berbuat banyak untuk file, ini mungkin tampak jelas. Tapi, untuk tujuan saya, ini membuatnya jauh lebih lambat daripada hanya membuka seluruh file dan membacanya menjadi string, seperti di atas.

Saat ini saya menjalankan script seperti di atas

$ pythonscript textfilename1 > textfilename2

Tapi saya ingin bisa menjalankannya (dan saudara-saudaranya) di pipa, seperti

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

Jawaban:

9

Kenapa tidak adil?

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

for file in files:
    f = open(file)
    ...
Mikel
sumber
12
sys.stdinharus digunakan sebagai gantinya karena lebih portabel daripada jalur hardcoded ke file.
Piotr Dobrogost
sys.stdinharus digunakan sebagai gantinya, seperti kata Piotr
smci
Tapi sys.stdinini file, dan sudah terbuka, dan tidak boleh ditutup. Tidak mungkin untuk menangani seperti argumen file tanpa melewati rintangan.
Alex
@alexis Tentu, jika Anda ingin menutup f, atau ingin menggunakan manajer konteks, Anda memerlukan sesuatu yang lebih kompleks. Lihat jawaban baru saya sebagai alternatif.
Mikel
12

Periksa apakah nama file diberikan sebagai argumen, atau dibaca sys.stdin.

Sesuatu seperti ini:

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

Ini mirip dengan jawaban Mikel kecuali ia menggunakan sysmodul. Saya pikir jika mereka memilikinya di sana pasti karena suatu alasan ...

rahmu
sumber
Bagaimana jika dua nama file ditentukan pada baris perintah?
Mikel
3
Oh tentu saja! Saya tidak repot menunjukkannya karena sudah ditunjukkan dalam jawaban Anda. Pada titik tertentu Anda harus mempercayai pengguna untuk memutuskan apa yang dia butuhkan. Tapi silakan edit jika Anda yakin ini yang terbaik. Maksud saya hanya untuk mengganti "open(/dev/stdin")dengan sys.stdin.
rahmu
2
Anda mungkin ingin memeriksa if len(sys.argv)>1:alih-alih if sys.argv[1]:jika tidak, Anda mendapatkan indeks di luar rentang kesalahan
Yibo Yang
3

Cara yang saya sukai untuk melakukannya ternyata ... (dan ini diambil dari blog Linux kecil yang menyenangkan bernama 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()

Alasan mengapa saya paling menyukai ini adalah, seperti yang dikatakan blogger, itu hanya menghasilkan pesan konyol jika tidak sengaja dipanggil tanpa masukan. Ini juga slot begitu baik ke semua skrip Python saya yang ada sehingga saya telah memodifikasi semuanya untuk memasukkannya.

ixtmixilix
sumber
3
Terkadang Anda ingin memasukkan input secara interaktif dari tty; memeriksa isattydan membatalkan tidak sesuai dengan filosofi filter Unix.
musiphil
Terlepas dari isattykutil, ini mencakup dasar yang berguna dan penting yang tidak ditemukan dalam jawaban lain, sehingga mendapat saya upvote.
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
sumber
Ini adalah bagaimana saya akan menulisnya, jika /dev/stdintidak tersedia di semua sistem saya.
Mikel
0

Saya menggunakan solusi ini dan berfungsi seperti pesona. Sebenarnya saya menggunakan dalam script calle unaccent yang menurunkan dan menghilangkan aksen dari string yang diberikan

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

Saya kira waktu terberat saya melihat solusi ini ada di sini .

SergioAraujo
sumber
0

Jika sistem Anda tidak memiliki /dev/stdin, atau Anda menginginkan solusi yang lebih umum, Anda dapat mencoba sesuatu yang lebih rumit seperti:

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
sumber
Mengapa Anda memindahkan penunjuk file saat keluar? Ide buruk. Jika input diarahkan dari file, program selanjutnya akan membacanya lagi. (Dan jika stdin adalah terminal, mencari biasanya tidak melakukan apa-apa, kan?) Biarkan saja.
alexis
Ya, sudah selesai. Saya hanya berpikir itu lucu untuk digunakan -beberapa kali. :)
Mikel