Як змусити програми python вести себе як належні інструменти Unix?


24

У мене є кілька сценаріїв Python, і я працюю над їх перезаписом. У мене однакова проблема з усіма ними.

Мені не очевидно, як писати програми, щоб вони поводилися як належні інструменти Unix.

Тому що це

$ cat characters | progname

і це

$ progname characters

повинні давати однаковий вихід.

Найближче, що я міг знайти в Python, це бібліотека файлових входів. На жаль, я не знаю, як переписати свої сценарії Python, які виглядають приблизно так:

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

Бібліотека fileinput обробляє stdin, якщо є stdin, і обробляє файл, якщо є файл. Але воно повторюється по одних рядках.

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

Я справді цього не розумію. Я думаю, якщо ви маєте справу з невеликими файлами, або якщо ви не дуже робите файли, це може здатися очевидним. Але в моїх цілях це робить набагато повільніше, ніж просто відкривати весь файл і читати його в рядок, як вище.

В даний час я запускаю сценарій вище, як

$ pythonscript textfilename1 > textfilename2

Але я хочу вміти запускати його (і його братів) по трубах, як

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

Відповіді:


9

Чому б не просто

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

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

12
sys.stdinйого слід використовувати замість цього, оскільки він більш портативний, ніж твердо кодований шлях до файлу.
Пьотр Доброгост

sys.stdinйого слід використовувати замість цього, як каже Пьотр
smci

Але sys.stdinце файл, і він уже відкритий, і його не можна закривати. Неможливо обробити так само, як аргумент файлу, не перестрибуючи обручі.
alexis

@alexis Звичайно, якщо ви хочете закрити fабо хочете використовувати контекстний менеджер, вам потрібно щось складніше. Дивіться мою нову відповідь як альтернативу.
Мікель

12

Перевірте, чи вказано ім'я файлу в якості аргументу, чи прочитане з нього sys.stdin.

Щось на зразок цього:

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

Це схоже на відповідь Мікеля, за винятком того, що він використовує sysмодуль. Я думаю, якщо вони там є, це повинно бути з причини ...


Що робити, якщо в командному рядку вказано два імені файлів?
Мікель

3
О, абсолютно! Я не потурбував це показати, тому що це вже було показано у вашій відповіді. У якийсь момент вам доведеться довіряти користувачеві, щоб вирішити, що їй потрібно. Але сміливо редагуйте, якщо вважаєте, що це найкраще. Моя думка лише замінити "open(/dev/stdin")на sys.stdin.
rahmu

2
ви можете перевірити if len(sys.argv)>1:замість того, if sys.argv[1]:інакше ви отримаєте індекс поза помилкою діапазону
Yibo Yang,

3

Мій бажаний спосіб зробити це, як виявляється, ... (і це взято з гарного маленького блогу Linux під назвою 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()

Причина, чому мені це найбільше сподобалось, полягає в тому, що, як каже блогер, він просто видає нерозумне повідомлення, якщо випадково зателефонував без введення. Він також настільки добре вписується у всі мої існуючі сценарії Python, що я змінив їх усі, щоб включити його.


3
Іноді ви хочете ввести вхід інтерактивно з tty; перевірка isattyта здача не відповідає філософії фільтрів Unix.
musiphil

Окрім isattyбородавки, це стосується корисного та важливого підґрунтя, яке не зустрічається в інших відповідях, тому воно отримує мою нагоду.
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)

Так я написав би це, якби він /dev/stdinбув недоступний у всіх моїх системах.
Мікель

0

Я використовую це рішення, і це працює як шарм. Насправді я використовую в скрипті виклик без уваги, що малі регістри та видаляє наголоси з заданого рядка

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

Я думаю , час firest я бачив це рішення було тут .


0

Якщо у вашій системі немає /dev/stdin, або ви хочете більш загального рішення, ви можете спробувати щось складніше:

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:
            ...

Чому ви переміщуєте вказівник на файл при виході? Погана ідея. Якщо вхід було переспрямовано з файлу, наступна програма знову прочитає його. (А якщо stdin - це термінал, пошук зазвичай нічого не робить, правда?) Просто залиште його в спокої.
alexis

Так, зробили. Я просто думав, що було мило використовувати -кілька разів. :)
Мікель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.