Como faço para que programas python se comportem como ferramentas unix apropriadas?

24

Eu tenho alguns scripts Python por aí e estou trabalhando para reescrevê-los. Eu tenho o mesmo problema com todos eles.

Não é óbvio para mim como escrever os programas para que eles se comportem como ferramentas unix apropriadas.

Porque isso

$ cat characters | progname

e isto

$ progname characters

deve produzir a mesma saída.

A coisa mais próxima que pude encontrar no Python foi a biblioteca de entrada de arquivos. Infelizmente, eu realmente não vejo como reescrever meus scripts Python, todos com a seguinte aparência:

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

A biblioteca fileinput processa stdin se houver um stdin e processa um arquivo se houver um arquivo. Mas ele itera sobre linhas únicas.

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

Eu realmente não entendo isso. Eu acho que se você estiver lidando com arquivos pequenos ou se não estiver fazendo muito com os arquivos, isso pode parecer óbvio. Mas, para meus propósitos, isso torna muito mais lento do que simplesmente abrir o arquivo inteiro e lê-lo em uma string, como acima.

Atualmente eu corro o script acima como

$ pythonscript textfilename1 > textfilename2

Mas eu quero poder executá-lo (e seus irmãos) em tubos, como

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

Respostas:

9

Por que não apenas

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

for file in files:
    f = open(file)
    ...
Mikel
fonte
12
sys.stdindeve ser usado, pois é mais portátil do que o caminho codificado para o arquivo.
Piotr Dobrogost 03/02
sys.stdindeve ser usado em vez disso, como diz Piotr
smci 11/11
Mas sys.stdiné um arquivo, e já está aberto e não deve ser fechado. Impossível lidar como um argumento de arquivo sem pular os bastidores.
alexis
@alexis Claro, se você deseja fechar fou usar um gerenciador de contexto, precisa de algo mais complexo. Veja minha nova resposta como uma alternativa.
Mikel
12

Verifique se um nome de arquivo é fornecido como argumento, ou então leia de sys.stdin.

Algo assim:

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

É semelhante à resposta de Mikel, exceto que ele usa o sysmódulo. Eu acho que se eles tiverem lá, deve ser por uma razão ...

rahmu
fonte
E se dois nomes de arquivos forem especificados na linha de comando?
Mikel
3
Oh absolutamente! Não me incomodei em mostrá-lo, porque ele já foi mostrado na sua resposta. Em algum momento, você precisa confiar no usuário para decidir o que ele precisa. Mas fique à vontade para editar se achar que isso é melhor. Meu ponto é apenas para substituir "open(/dev/stdin")com sys.stdin.
rahmu
2
você pode querer verificar em if len(sys.argv)>1:vez de if sys.argv[1]:obter um erro de índice fora do intervalo
Yibo Yang
3

Minha maneira preferida de fazer isso acaba sendo ... (e isso é retirado de um pequeno e agradável blog do Linux chamado 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()

A razão pela qual eu mais gostei disso é que, como diz o blogueiro, ela apenas envia uma mensagem boba se for chamada acidentalmente sem entrada. Ele também se encaixa tão bem em todos os meus scripts Python existentes que os modifiquei para incluí-lo.

ixtmixilix
fonte
3
Às vezes, você deseja inserir a entrada interativamente a partir de um tty; A verificação isattye resgate não está de acordo com a filosofia dos filtros Unix.
Musiphil #
Além da isattyverruga, isso cobre um terreno útil e importante que não foi encontrado nas outras respostas, de modo que obtém meu voto positivo.
Tripleee 23/09/2015
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
fonte
É assim que eu teria escrito, se /dev/stdinnão estivesse disponível em todos os meus sistemas.
22618 Mikel
0

Estou usando esta solução e funciona como um encanto. Na verdade, eu estou usando em um script calle unaccent que minúsculas e remove acentos de uma determinada string

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

Acho que o melhor momento em que vi essa solução foi aqui .

SergioAraujo
fonte
0

Se o seu sistema não possui /dev/stdin, ou você deseja uma solução mais geral, tente algo mais complicado, como:

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
fonte
Por que você move o ponteiro do arquivo na saída? Péssima ideia. Se a entrada foi redirecionada de um arquivo, o próximo programa o lerá novamente. (E se stdin for um terminal, procure normalmente não faz nada, certo?) Apenas deixe em paz.
alexis
Sim, pronto. Eu apenas pensei que era fofo usar -várias vezes. :)
Mikel