¿Cómo hago que los programas de Python se comporten como herramientas Unix adecuadas?


24

Tengo algunos scripts de Python por ahí, y estoy trabajando en reescribirlos. Tengo el mismo problema con todos ellos.

No es obvio para mí cómo escribir los programas para que se comporten como herramientas Unix adecuadas.

Porque esto

$ cat characters | progname

y esto

$ progname characters

debería producir la misma salida.

Lo más parecido que pude encontrar a eso en Python fue la biblioteca de entrada de archivos. Desafortunadamente, realmente no veo cómo reescribir mis scripts de Python, todos los cuales se ven así:

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

La biblioteca de entrada de archivos procesa stdin si hay un stdin, y procesa un archivo si hay un archivo. Pero itera sobre líneas simples.

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

Realmente no entiendo eso. Supongo que si se trata de archivos pequeños, o si no está haciendo mucho a los archivos, esto puede parecer obvio. Pero, para mis propósitos, esto lo hace mucho más lento que simplemente abrir todo el archivo y leerlo en una cadena, como se indicó anteriormente.

Actualmente ejecuto el script anterior como

$ pythonscript textfilename1 > textfilename2

Pero quiero poder ejecutarlo (y sus hermanos) en tuberías, como

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

Respuestas:


9

¿Por qué no solo

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

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

12
sys.stdindebe usarse en su lugar, ya que es más portátil que la ruta codificada al archivo.
Piotr Dobrogost

sys.stdindebería usarse en su lugar, como dice Piotr
smci

Pero sys.stdines un archivo, y ya está abierto, y no debe cerrarse. Imposible de manejar como un argumento de archivo sin saltar a través de aros.
alexis

@alexis Claro, si desea cerrar fo usar un administrador de contexto, necesita algo más complejo. Vea mi nueva respuesta como alternativa.
Mikel

12

Compruebe si se proporciona un nombre de archivo como argumento, o si no se lee sys.stdin.

Algo como esto:

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

Es similar a la respuesta de Mikel, excepto que usa el sysmódulo. Me imagino que si lo tienen allí debe ser por una razón ...


¿Qué sucede si se especifican dos nombres de archivo en la línea de comando?
Mikel

3
Oh absolutamente! No me molesté en mostrarlo porque ya estaba en tu respuesta. En algún momento, debe confiar en el usuario para decidir qué necesita. Pero siéntase libre de editar si cree que esto es lo mejor. Mi punto es solo reemplazar "open(/dev/stdin")con sys.stdin.
rahmu

2
es posible que desee comprobar if len(sys.argv)>1: lugar de lo if sys.argv[1]:contrario obtendrá un error de índice fuera de rango
Yibo Yang

3

Mi forma preferida de hacerlo resulta ser ... (y esto está tomado de un pequeño y lindo blog de Linux llamado 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()

La razón por la que me gustó más esto es que, como dice el blogger, solo emite un mensaje tonto si se llama accidentalmente sin entrada. También se integra tan bien en todos mis scripts de Python existentes que los modifiqué para incluirlos.


3
A veces desea ingresar la entrada interactivamente desde un tty; la verificación isattyy el rescate no se ajusta a la filosofía de los filtros Unix.
musiphil

Además de la isattyverruga, esto cubre un terreno útil e importante que no se encuentra en las otras respuestas, por lo que recibe mi voto positivo.
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)

Así es como lo habría escrito, si /dev/stdinno estuviera disponible en todos mis sistemas.
Mikel

0

Estoy usando esta solución y funciona de maravilla. En realidad estoy usando una secuencia de comandos que no está en acento que en minúsculas y elimina los acentos de una cadena dada

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

Creo que el mejor momento en que vi esta solución fue aquí .


0

Si su sistema no tiene /dev/stdin, o si desea una solución más general, puede intentar algo más 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:
            ...

¿Por qué mueves el puntero del archivo al salir? Mala idea. Si la entrada se redirigió desde un archivo, el siguiente programa lo leerá nuevamente. (Y si stdin es una terminal, la búsqueda generalmente no hace nada, ¿verdad?) Solo déjalo en paz.
alexis

Sí, listo Solo pensé que era lindo usarlo -varias veces. :)
Mikel
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.