Equivalente de Bash Backticks en Python [duplicado]

84

¿Cuál es el equivalente de las comillas invertidas que se encuentran en Ruby y Perl en Python? Es decir, en Ruby puedo hacer esto:

foo = `cat /tmp/baz`

¿Cómo se ve la declaración equivalente en Python? Lo intenté, os.system("cat /tmp/baz")pero eso pone el resultado en estándar y me devuelve el código de error de esa operación.

Chris Bunch
fuente

Respuestas:

100
output = os.popen('cat /tmp/baz').read()
John Kugelman
fuente
4
@mckenzm La pregunta se trata de capturar el resultado de un proceso externo. Capturar la salida de una función de Python sería una cuestión bastante diferente.
John Kugelman
1
Maravillosamente conciso, y de hecho el equivalente de Ruby `...`(capturando stdout, pasando stderr a través) - con una excepción: Ruby permite determinar el código de salida del proceso vía $?después del hecho; en Python, por lo que puedo decir, tendrás que usar las subprocessfunciones del módulo para eso.
mklement0
82

La forma más flexible es utilizar el subprocessmódulo:

import subprocess

out = subprocess.run(["cat", "/tmp/baz"], capture_output=True)
print("program output:", out)

capture_outputse introdujo en Python 3.7, para versiones anteriores check_output()se puede usar la función especial en su lugar:

out = subprocess.check_output(["cat", "/tmp/baz"])

También puede construir manualmente un objeto de subproceso si necesita un control detallado:

proc = subprocess.Popen(["cat", "/tmp/baz"], stdout=subprocess.PIPE)
(out, err) = proc.communicate()

Todas estas funciones admiten parámetros de palabras clave para personalizar cómo se ejecuta exactamente el subproceso. Por ejemplo, puede usar shell=Truepara ejecutar el programa a través del shell, si necesita cosas como expansiones de nombres de archivos *, pero eso tiene limitaciones .

algo
fuente
2
sí, esta es la única forma sensata, podría envolverlo en una función para poder llamar a algo como ejecutar ("comando")
Vinko Vrsalovic
En realidad, esto no funciona para mí, ya que en este caso, baz es un directorio y estoy tratando de obtener el contenido de todos los archivos en ese directorio. (hacer cat / tmp / baz / * funciona en ticks pero no a través del método descrito aquí)
Chris Bunch
6
re: "*" no funciona; use subprocess.Popen (["cat", "/ tmp / baz"], stdout = subprocess.PIPE, shell = True) en su lugar. Dado que la expansión glob (estrella) es manejada por shell, el módulo de subprocesamiento debe usar la expansión de shell en este caso (proporcionada por / bin / sh).
Pasi Savolainen
1
De docs.python.org/2/library/subprocess.html#popen-constructor : "(con shell = True) Si args es una secuencia, el primer elemento especifica la cadena de comando y cualquier elemento adicional se tratará como argumento adicional al propio caparazón ". Entonces, si va a usar shell = True, entonces el primer argumento probablemente debería ser la cadena "cat / tmp / baz". Alternativamente, si desea usar una secuencia como primer argumento, debe usar shell = False
ninguno
1
@gerrit: no está en desuso. Los documentos recomiendan subprocess.run() (no sé si se merece) si no necesita admitir versiones anteriores o si no necesita la flexibilidad proporcionada por Popen().
jfs
27

algo tiene razón . También puede usar os.popen (), pero cuando esté disponible (Python 2.4+), generalmente es preferible el subproceso.

Sin embargo, a diferencia de algunos lenguajes que lo fomentan, generalmente se considera de mala forma generar un subproceso en el que se puede hacer el mismo trabajo dentro del lenguaje. Es más lento, menos confiable y depende de la plataforma. Su ejemplo estaría mejor si:

foo= open('/tmp/baz').read()

eta:

baz es un directorio y estoy tratando de obtener el contenido de todos los archivos en ese directorio

? cat en un directorio me da un error.

Si desea una lista de archivos:

import os
foo= os.listdir('/tmp/baz')

Si desea el contenido de todos los archivos en un directorio, algo como:

contents= []
for leaf in os.listdir('/tmp/baz'):
    path= os.path.join('/tmp/baz', leaf)
    if os.path.isfile(path):
        contents.append(open(path, 'rb').read())
foo= ''.join(contents)

o, si puede estar seguro de que no hay directorios allí, puede colocarlo en una sola línea:

path= '/tmp/baz'
foo= ''.join(open(os.path.join(path, child), 'rb').read() for child in os.listdir(path))
bobince
fuente
1
Aunque esta no fue una respuesta a la pregunta, es la mejor respuesta para educar a los usuarios.
noamtm
1
El título de la pregunta es "¿cuál es el equivalente a comillas invertidas?". Supuse que "gato" era solo un comando de ejemplo. Esta respuesta no ayuda con el caso general.
Jason
15
foo = subprocess.check_output(["cat", "/tmp/baz"])
jfs
fuente
3
Esta es la forma más sencilla ahora. "subprocess.check_output" se agregó en Python 2.7, que fue lanzado en julio de 2010, después de que se dieran las otras respuestas "popen".
Robert Fleming
10

Desde Python 3.5 en adelante, la forma recomendada es usar subprocess.run. Desde Python 3.7, para obtener el mismo comportamiento que describe, usaría:

cpe = subprocess.run("ls", shell=True, capture_output=True)

Esto devolverá un subprocess.CompletedProcessobjeto. La salida a stdout estará adentro cpe.stdout, la salida a stderr estará adentro cpe.stderr, que serán ambos bytesobjetos. Puede decodificar la salida para obtener un strobjeto utilizando cpe.stdout.decode()u obtener un pasando text=Truea subprocess.run:

cpe = subprocess.run("ls", shell=True, capture_output=True, text=True)

En el último caso, cpe.stdouty cpe.stderrson ambos strobjetos.

gerrit
fuente
Desde python 3.7 en adelante, puede usar el text=Trueparámetro para recuperar una cadena en lugar de bytes.
bwv549
6

La forma más sencilla es utilizar el paquete de comandos.

import commands

commands.getoutput("whoami")

Salida:

'bganesan'

Balachander Ganesan
fuente
6
Muy fácil, pero el módulo ahora está obsoleto.
Gringo Suave
3
import os
foo = os.popen('cat /tmp/baz', 'r').read()
awatts
fuente
3
Este es el equivalente a las comillas invertidas de Ruby, pero si su problema es enumerar el contenido de un directorio, esta no es la mejor manera de hacerlo.
awatts el
2

Estoy usando

(6: 0) $ python --versión Python 2.7.1

Uno de los ejemplos anteriores es:

import subprocess
proc = subprocess.Popen(["cat", "/tmp/baz"], stdout=subprocess.PIPE, shell=True)
(out, err) = proc.communicate()
print "program output:", out

Para mí, esto no pudo acceder al directorio / tmp. Después de mirar la cadena doc para el subproceso, reemplacé

["prog", "arg"]

con

"prog arg"

y obtuvo el comportamiento de expansión de shell que se deseaba (a la Perl's `prog arg`)

print subprocess.Popen ("ls -ld / tmp / v *", stdout = subprocess.PIPE, shell = True) .communicate () [0]


Dejé de usar Python hace un tiempo porque me molestaba la dificultad de hacer el equivalente de perl `cmd ...`. Me alegra descubrir que Python ha hecho esto razonable.

funkyj
fuente
1

Si usa subprocess.Popen, recuerde especificar bufsize. El valor predeterminado es 0, que significa "sin búfer", no "elija un valor predeterminado razonable".

Jorge
fuente
1

Esto no funcionará en python3, pero en python2 puede extender strcon un __repr__método personalizado que llama a su comando de shell y lo devuelve así:

#!/usr/bin/env python

import os

class Command(str):
    """Call system commands"""

    def __repr__(cmd):
        return os.popen(cmd).read()

Que puedes usar como

#!/usr/bin/env python
from command import Command

who_i_am = `Command('whoami')`

# Or predeclare your shell command strings
whoami = Command('whoami')
who_i_am = `whoami`
ThorInvocador
fuente
4
Además, probablemente no deberías hacer esto *
ThorSummoner
-1

repr()

El backtickoperador (`) se eliminó en Python 3. Es confusamente similar a una comilla simple y difícil de escribir en algunos teclados. En lugar de backtick, utilice la función incorporada equivalente repr().

Pedro Lobito
fuente
Eso no es el equivalente a las comillas invertidas de bash.
gerrit