Equivalent des Backticks Bash en Python [duplicate]

84

Quel est l'équivalent des backticks trouvés dans Ruby et Perl en Python? Autrement dit, dans Ruby, je peux faire ceci:

foo = `cat /tmp/baz`

À quoi ressemble la déclaration équivalente en Python? J'ai essayé os.system("cat /tmp/baz")mais cela met le résultat à la norme et me renvoie le code d'erreur de cette opération.

Chris Bunch
la source

Réponses:

100
output = os.popen('cat /tmp/baz').read()
John Kugelman
la source
4
@mckenzm La question concerne la capture de la sortie d'un processus externe. Capturer la sortie d'une fonction Python serait une question très différente.
John Kugelman
1
Merveilleusement concis, et même l'équivalent de Ruby `...`(capturer stdout, passer stderr through) - à une exception près: Ruby permet de déterminer le code de sortie du processus $?après coup; en Python, d'après ce que je peux dire, vous devrez utiliser les subprocessfonctions du module pour cela.
mklement0
82

La manière la plus flexible est d'utiliser le subprocessmodule:

import subprocess

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

capture_outputa été introduit dans Python 3.7, pour les anciennes versions, la fonction spéciale check_output()peut être utilisée à la place:

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

Vous pouvez également créer manuellement un objet de sous-processus si vous avez besoin d'un contrôle précis:

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

Toutes ces fonctions prennent en charge les paramètres de mots-clés pour personnaliser la façon dont le sous-processus est exécuté exactement. Vous pouvez par exemple utiliser shell=Truepour exécuter le programme via le shell, si vous avez besoin de choses comme des extensions de nom de fichier de *, mais cela vient avec des limitations .

qc
la source
2
oui, c'est la seule manière sensée, vous pouvez l'envelopper dans une fonction afin que vous puissiez appeler quelque chose comme execute ("command")
Vinko Vrsalovic
Cela ne fonctionne pas pour moi, car dans ce cas, baz est un répertoire et j'essaye d'obtenir le contenu de tous les fichiers de ce répertoire. (faire cat / tmp / baz / * fonctionne dans les ticks mais pas via la méthode décrite ici)
Chris Bunch
6
re: "*" ne fonctionne pas; utilisez plutôt subprocess.Popen (["cat", "/ tmp / baz"], stdout = subprocess.PIPE, shell = True). Puisque l'expansion glob (étoile) est gérée par le shell, le module de sous-traitement doit utiliser l'expansion du shell dans ce cas (fourni par / bin / sh).
Pasi Savolainen le
1
De docs.python.org/2/library/subprocess.html#popen-constructor : "(with shell = True) Si args est une séquence, le premier élément spécifie la chaîne de commande, et tous les éléments supplémentaires seront traités comme des arguments supplémentaires à la coque elle-même. " Donc, si vous allez utiliser shell = True, alors le premier argument devrait probablement être la chaîne "cat / tmp / baz". Alternativement, si vous voulez utiliser une séquence comme premier argument, vous devez utiliser shell = False
aucun
1
@gerrit: il n'est pas obsolète. La documentation recommande subprocess.run() (je ne sais pas si cela est mérité) si vous n'avez pas besoin de prendre en charge les versions antérieures ou si vous n'avez pas besoin de la flexibilité fournie par Popen().
jfs
27

qc a raison . Vous pouvez également utiliser os.popen (), mais le sous-processus disponible (Python 2.4+) est généralement préférable.

Cependant, contrairement à certains langages qui l'encouragent, il est généralement considéré comme une mauvaise forme de générer un sous-processus où vous pouvez faire le même travail dans le langage. C'est plus lent, moins fiable et dépendant de la plate-forme. Votre exemple serait mieux car:

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

eta:

baz est un répertoire et j'essaye d'obtenir le contenu de tous les fichiers de ce répertoire

? chat sur un répertoire me donne une erreur.

Si vous voulez une liste de fichiers:

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

Si vous voulez le contenu de tous les fichiers dans un répertoire, quelque chose comme:

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)

ou, si vous pouvez être sûr qu'il n'y a pas de répertoires là-dedans, vous pouvez l'inclure dans une seule ligne:

path= '/tmp/baz'
foo= ''.join(open(os.path.join(path, child), 'rb').read() for child in os.listdir(path))
bobince
la source
1
Bien que ce ne soit pas une réponse à la question, c'est la meilleure réponse pour éduquer les utilisateurs.
noamtm
1
Le titre de la question est "quel est l'équivalent des backticks". J'ai supposé que "chat" n'était qu'un exemple de commande. Cette réponse n'aide pas avec le cas général.
Jason
15
foo = subprocess.check_output(["cat", "/tmp/baz"])
jfs
la source
3
C'est le moyen le plus simple maintenant. "subprocess.check_output" a été ajouté dans Python 2.7, qui a été publié en juillet 2010, après que les autres réponses "popen" aient été données.
Robert Fleming
10

À partir de Python 3.5, la méthode recommandée est d'utiliser subprocess.run. Depuis Python 3.7, pour obtenir le même comportement que celui que vous décrivez, vous utiliseriez:

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

Cela renverra un subprocess.CompletedProcessobjet. La sortie vers stdout sera dans cpe.stdout, la sortie vers stderr sera dans cpe.stderr, qui seront tous les deux des bytesobjets. Vous pouvez décoder la sortie pour obtenir un strobjet en utilisant cpe.stdout.decode()ou obtenir un en passant text=Trueà subprocess.run:

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

Dans ce dernier cas, cpe.stdoutet cpe.stderrsont tous deux des strobjets.

gerrit
la source
À partir de python 3.7, vous pouvez utiliser le text=Trueparamètre pour récupérer une chaîne au lieu d'octets.
bwv549
6

Le moyen le plus simple est d'utiliser le package de commandes.

import commands

commands.getoutput("whoami")

Production:

«bganesan»

Balachander Ganesan
la source
6
Très simple, mais le module est désormais obsolète.
Gringo Suave le
3
import os
foo = os.popen('cat /tmp/baz', 'r').read()
awatts
la source
3
C'est l'équivalent des backticks de Ruby, mais si votre problème est de lister le contenu d'un répertoire, ce n'est pas la meilleure façon de le faire.
awatts
2

j'utilise

(6: 0) $ python - version Python 2.7.1

L'un des exemples ci-dessus est:

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

Pour moi, cela n'a pas réussi à accéder au répertoire / tmp. Après avoir regardé la chaîne doc pour le sous-processus que j'ai remplacé

["prog", "arg"]

avec

"prog arg"

et a obtenu le comportement d'extension du shell souhaité (à la Perl's `prog arg`)

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


J'ai arrêté d'utiliser python il y a quelque temps car j'étais ennuyé par la difficulté de faire l'équivalent de perl `cmd ...`. Je suis heureux de constater que Python a rendu cela raisonnable.

funkyj
la source
1

Si vous utilisez subprocess.Popen, n'oubliez pas de spécifier bufsize. La valeur par défaut est 0, ce qui signifie «sans tampon», et non «choisir une valeur par défaut raisonnable».

George
la source
1

Cela ne fonctionnera pas en python3, mais en python2, vous pouvez étendre stravec une __repr__méthode personnalisée qui appelle votre commande shell et la renvoie comme suit:

#!/usr/bin/env python

import os

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

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

Que vous pouvez utiliser comme

#!/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`
ThorSummoner
la source
4
De plus, vous ne devriez probablement pas faire cela *
ThorSummoner
-1

repr()

L' backtickopérateur (`) a été supprimé dans Python 3. Il est confusément similaire à un guillemet simple et difficile à taper sur certains claviers. Au lieu de backtick, utilisez la fonction intégrée équivalente repr().

Pedro Lobito
la source
Ce n'est pas l'équivalent des backticks bash.
gerrit