Как заставить программы на Python вести себя как надлежащие инструменты Unix?

24

У меня есть несколько скриптов Python, и я работаю над их переписыванием. У меня та же проблема со всеми из них.

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

Потому что это

$ cat characters | progname

и это

$ progname characters

должен выдавать тот же результат.

Наиболее близкой вещью, которую я смог найти в Python, была библиотека fileinput. К сожалению, я не вижу, как переписать мои скрипты 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
ixtmixilix
источник
5
Смотрите здесь: stackoverflow.com/questions/1744989/read-from-file-or-stdin
Мат

Ответы:

9

Почему не просто

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

for file in files:
    f = open(file)
    ...
Mikel
источник
12
sys.stdinследует использовать вместо этого, поскольку он более переносим, ​​чем жестко заданный путь к файлу.
Петр Доброгост
sys.stdinследует использовать вместо этого, как говорит Петр
smci
Но sys.stdinэто файл, и он уже открыт, и не должен быть закрыт. Невозможно обработать, как аргумент файла, не перепрыгивая через обручи.
Алексис
@alexis Конечно, если вы хотите закрыть fили использовать менеджер контекста, вам нужно что-то более сложное. Смотрите мой новый ответ в качестве альтернативы.
Микель
12

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

Что-то вроде этого:

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

Это похоже на ответ Микеля, за исключением того, что он использует sysмодуль. Я полагаю, если у них есть это там, это должно быть по причине ...

rahmu
источник
Что, если в командной строке указаны два имени файла?
Микель
3
О, абсолютно! Я не стал показывать это, потому что это уже было показано в вашем ответе. В какой-то момент вы должны доверять пользователю, чтобы решить, что ему нужно. Но не стесняйтесь редактировать, если считаете, что это лучше. Моя точка зрения состоит только заменить "open(/dev/stdin")с sys.stdin.
Рахму
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, что я изменил их все, чтобы включить.

ixtmixilix
источник
3
Иногда вы хотите ввести ввод в интерактивном режиме из tty; проверка isattyи выручка не соответствуют философии Unix-фильтров.
Musiphil
Помимо isattyбородавки, это охватывает полезную и важную почву, которой нет в других ответах, так что это вызывает у меня одобрение.
Трипли
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
источник
Вот как бы я написал это, если бы /dev/stdinбыл недоступен на всех моих системах.
Микель
0

Я использую это решение, и оно работает как шарм. На самом деле я использую в скрипте Каллы unaccent , что нижний регистр и удаляют акценты из заданной строки

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

Я думаю, самое жаркое время, когда я увидел это решение, было здесь .

SergioAraujo
источник
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:
            ...
Mikel
источник
Почему вы перемещаете указатель файла при выходе? Плохая идея. Если ввод был перенаправлен из файла, следующая программа прочитает его снова. (И если stdin является терминалом, поиск обычно ничего не делает, верно?) Просто оставьте это в покое.
Алексис
Да, готово. Я просто подумал, что это мило использовать -несколько раз. :)
Микель