¿Cómo leer desde stdin o desde un archivo si no se canalizan datos en Python?

Tengo un script de CLI y quiero que lea datos de un archivo. Debería poder leerlo de dos maneras:

  • cat data.txt | ./my_script.py
  • ./my_script.py data.txt

—Un poco como grep , por ejemplo.

Lo que yo sé:

  • sys.argv y optparse me permiten leer cualquier argumento y opciones fácilmente.
  • sys.stdin me dejó leer los datos introducidos
  • fileinput hacer el proceso completo automático

Desafortunadamente:

  • usando fileinput usa stdin y cualquier argumento como entrada. Así que no puedo usar opciones que no sean nombres de archivo cuando intenta abrirlos.
  • sys.stdin.readlines() funciona bien, pero si no canalizo ningún dato, se bloquea hasta que ingrese Ctrl + D
  • No sé cómo implementar “si no hay nada en la entrada estándar, lea de un archivo en args” porque la stdin siempre es True en un contexto booleano.

Me gustaría una forma portátil de hacer esto si es posible.

Procese sus argumentos sin nombre de archivo como desee, de modo que termine con una matriz de argumentos sin opción, luego pase esa matriz como parámetro a fileinput.input ():

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

Argparse permite que esto se haga de una manera bastante fácil, y realmente debería usarlo en lugar de optparse menos que tenga problemas de compatibilidad.

El código iría algo como esto:

 import argparse parser = argparse.ArgumentParser() parser.add_argument('--input', type = argparse.FileType('r'), default = '-') 

Ahora tiene un analizador que analizará los argumentos de la línea de comando, use un archivo si lo ve o use la entrada estándar si no lo hace.

Para Unix / Linux, puede detectar si los datos se están canalizando mirando os.isatty(0)

 $ date | python -c "import os;print os.isatty(0)" False $ python -c "import os;print os.isatty(0)" True 

No estoy seguro de que haya un equivalente para Windows.

edit Ok, lo probé con python2.6 en Windows XP

 C:\Python26>echo "hello" | python.exe -c "import os;print os.isatty(0)" False C:\Python26> python.exe -c "import os;print os.isatty(0)" True 

Así que tal vez no sea todo sin esperanza para las ventanas.

Soy un noob, por lo que esta podría no ser una buena respuesta, pero estoy tratando de hacer lo mismo (permitir uno o más archivos en la línea de comandos, de manera predeterminada, STDIN).

El combo final que armé:

 parser = argparse.ArgumentParser() parser.add_argument("infiles", nargs="*") args = parser.parse_args() for line in fileinput.input(args.infiles): process(line) 

Esta parece ser la única forma de obtener todo el comportamiento deseado en un paquete elegante, sin necesidad de argumentos con nombre. Al igual que los comandos de Unix se utilizan como tales:

 cat file1 file2 wc -l < file1 

No:

 cat --file file1 --file file2 

Agradecería la retroalimentación / confirmación de los veteranos pitonistas idiomáticos para asegurarse de que tengo la mejor respuesta. No he visto esta solución completa mencionada en ningún otro lugar, solo fragmentos.

No hay una manera confiable de detectar si sys.stdin está conectado a algo, ni es apropiado hacerlo (por ejemplo, el usuario quiere pegar los datos). Detecta la presencia de un nombre de archivo como un argumento y usa stdin si no se encuentra ninguno.

Puede usar esta función para detectar si la entrada proviene de una tubería o no.

 sys.stdin.isatty() 

Devuelve false si la entrada es de pipeline o true de lo contrario.