Effectuer des commandes via ssh avec Python


136

J'écris un script pour automatiser certaines commandes de ligne de commande en Python. En ce moment je fais des appels donc:

cmd = "some unix command"
retcode = subprocess.call(cmd,shell=True)

Cependant, j'ai besoin d'exécuter certaines commandes sur une machine distante. Manuellement, je me connectais en utilisant ssh puis j'exécutais les commandes. Comment pourrais-je automatiser cela en Python? Je dois me connecter avec un mot de passe (connu) à la machine distante, donc je ne peux pas simplement utiliser cmd = ssh user@remotehost, je me demande s'il y a un module que je devrais utiliser?

Réponses:


201

Je vais vous référer à paramiko

voir cette question

ssh = paramiko.SSHClient()
ssh.connect(server, username=username, password=password)
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command(cmd_to_execute)

3
L'hypothèse ici est que paramiko est aussi sûr que ssh (ouvert). C'est ça?
user239558

1
et si les clés ssh sont échangées?
Ardit

19
Si vous utilisez des clés SSH, préparez d'abord le fichier de clés en utilisant SOIT: k = paramiko.RSAKey.from_private_key_file(keyfilename)OU k = paramiko.DSSKey.from_private_key_file(keyfilename)PUIS ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())et enfin ssh..connect(hostname=host, username=user, pkey=k).
Scott Prive

7
En tant qu'utilisateur de longue date de Paramiko (mais pas un expert), je peux suggérer d'utiliser Paramiko, mais vous devez tenir compte de vos cas d'utilisation et de ce que vous êtes prêt à apprendre. Paramiko est de très bas niveau, et vous pouvez facilement tomber dans un piège où vous créez une "fonction d'aide à l'exécution de commandes" sans bien comprendre le code que vous utilisez. Cela signifie que vous pouvez concevoir un système def run_cmd(host, cmd):qui fait ce que vous voulez au début, mais vos besoins évoluent. Vous finissez par changer l'assistant pour le nouveau cas d'utilisation, ce qui change le comportement de l'ancienne utilisation existante. Planifiez en conséquence.
Scott Prive

3
Pour une erreur d'hôte inconnue, faites: ssh.set_missing_host_key_policy (paramiko.AutoAddPolicy ()) avant
Nemo

49

Ou vous pouvez simplement utiliser commands.getstatusoutput :

   commands.getstatusoutput("ssh machine 1 'your script'")

Je l'ai beaucoup utilisé et cela fonctionne très bien.

Dans Python 2.6+, utilisez subprocess.check_output.


4
+1 pour une belle méthode intégrée simple. Dans ma configuration actuelle, je ne veux pas ajouter de bibliothèques Python, donc votre suggestion est précieuse, très simple aussi.
Philip Kearns

3
assurez-vous simplement que votre hôte distant est configuré pour ssh sans mot de passe, sinon, vous devez faire d'autres choses pour gérer l'authentification
powerrox

subprocess.check_output - excellente solution!
Tim S.

2
@powerrox que sont ces "autres choses?"
ealeon

2
@TimS. vous devrez peut-être inclure la gestion de l'authentification par tout moyen approprié à votre configuration. J'avais l'habitude de m'attendre à entrer le mot de passe à l'invite. puis il y a ce fil avec d'autres solutions: unix.stackexchange.com/questions/147329/…
powerrox

29

Avez-vous jeté un œil à Fabric ? Il vous permet de faire toutes sortes de choses à distance via SSH en utilisant python.


18

J'ai trouvé que paramiko était un peu trop bas niveau et que Fabric n'était pas particulièrement bien adapté pour être utilisé comme bibliothèque, j'ai donc créé ma propre bibliothèque appelée spur qui utilise paramiko pour implémenter une interface légèrement plus agréable:

import spur

shell = spur.SshShell(hostname="localhost", username="bob", password="password1")
result = shell.run(["echo", "-n", "hello"])
print result.output # prints hello

Si vous devez exécuter à l'intérieur d'un shell:

shell.run(["sh", "-c", "echo -n hello"])

2
J'ai décidé d'essayer spur. Vous générez des commandes shell supplémentaires et vous vous retrouvez avec: quel 'mkdir'> / dev / null 2> & 1; echo $ ?; exec 'mkdir' '-p' '/ data / rpmupdate / 20130207142923'. Je voudrais aussi avoir accès à une plaine exec_command. Il manque également la capacité à exécuter des tâches d'arrière - plan: nohup ./bin/rpmbuildpackages < /dev/null >& /dev/null &. Par exemple, je génère un script zsh (rpmbuildpackages) en utilisant un modèle, puis je le laisse simplement fonctionner sur la machine. Peut-être que la capacité de surveiller de telles tâches en arrière-plan serait également intéressante (enregistrer les PID dans certains ~ / .spur).
davidlt

1
spurapparemment ne fonctionne que sur les systèmes Unix car il a une dépendance sur termios. Quelqu'un connaît-il une bonne bibliothèque pour Windows?
Gabriel

Pas tout à fait vrai: si vous utilisez un programme d' installation précompilé , vous pourrez installer paramiko et spur. Je viens de le faire moi-même ...
ravemir

@Gabriel: l'une des versions récentes devrait avoir amélioré le support sous Windows. Si cela ne fonctionne toujours pas, n'hésitez pas à ouvrir un problème.
Michael Williamson

@davidlt: lors de la construction d'un SshShell, il y a maintenant la possibilité de définir le type de shell. Si un shell minimal est utilisé en passant shell_type=spur.ssh.ShellTypes.minimal, alors seule la commande brute est envoyée. L'implémentation des tâches d'arrière-plan semble directement un peu hors de portée pour Spur, mais vous devriez pouvoir exécuter la commande que vous avez décrite en appelant un shell par exemple shell.run(["sh", "-c", "nohup ./bin/rpmbuildpackages < /dev/null >& /dev/null &"]).
Michael Williamson

18

Rester simple. Aucune bibliothèque requise.

import subprocess

subprocess.Popen("ssh {user}@{host} {cmd}".format(user=user, host=host, cmd='ls -l'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()

1
Subprocess est votre couteau suisse en automatisation. Vous pouvez alors combiner Python avec des possibilités infinies comme les scripts shell, sed, awk, grep. Oui, vous pouvez tous faire en Python, mais ne serait-il pas génial d'exécuter un grep quelque part en python - eh bien, vous le pouvez.
Ronn Macc le

12
si vos commandes ssh demandent un mot de passe, comment le fourniriez-vous dans un fichier Python?
BeingSuman

1
@BeingSuman Vous pouvez utiliser l'authentification par clé SSH pour cela. Bien que je suppose que vous avez déjà compris cela.
mmrbulbul

9

Tous ont déjà indiqué (recommandé) l'utilisation de paramiko et je ne fais que partager un code python (on peut dire API) qui vous permettra d'exécuter plusieurs commandes en une seule fois.

pour exécuter des commandes sur différents nœuds, utilisez: Commands().run_cmd(host_ip, list_of_commands)

Vous verrez un TODO, que j'ai gardé pour arrêter l'exécution si l'une des commandes ne s'exécute pas, je ne sais pas comment le faire. merci de partager vos connaissances

#!/usr/bin/python

import os
import sys
import select
import paramiko
import time


class Commands:
    def __init__(self, retry_time=0):
        self.retry_time = retry_time
        pass

    def run_cmd(self, host_ip, cmd_list):
        i = 0
        while True:
        # print("Trying to connect to %s (%i/%i)" % (self.host, i, self.retry_time))
        try:
            ssh = paramiko.SSHClient()
            ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
            ssh.connect(host_ip)
            break
        except paramiko.AuthenticationException:
            print("Authentication failed when connecting to %s" % host_ip)
            sys.exit(1)
        except:
            print("Could not SSH to %s, waiting for it to start" % host_ip)
            i += 1
            time.sleep(2)

        # If we could not connect within time limit
        if i >= self.retry_time:
            print("Could not connect to %s. Giving up" % host_ip)
            sys.exit(1)
        # After connection is successful
        # Send the command
        for command in cmd_list:
            # print command
            print "> " + command
            # execute commands
            stdin, stdout, stderr = ssh.exec_command(command)
            # TODO() : if an error is thrown, stop further rules and revert back changes
            # Wait for the command to terminate
            while not stdout.channel.exit_status_ready():
                # Only print data if there is data to read in the channel
                if stdout.channel.recv_ready():
                    rl, wl, xl = select.select([ stdout.channel ], [ ], [ ], 0.0)
                    if len(rl) > 0:
                        tmp = stdout.channel.recv(1024)
                        output = tmp.decode()
                        print output

        # Close SSH connection
        ssh.close()
        return

def main(args=None):
    if args is None:
        print "arguments expected"
    else:
        # args = {'<ip_address>', <list_of_commands>}
        mytest = Commands()
        mytest.run_cmd(host_ip=args[0], cmd_list=args[1])
    return


if __name__ == "__main__":
    main(sys.argv[1:])

Je vous remercie!


4

J'ai utilisé un tas de paramiko (sympa) et pxssh (également sympa). Je recommanderais non plus. Ils fonctionnent un peu différemment mais ont un chevauchement d'utilisation relativement important.


4
Le lien vers pxssh est un beau voyage dans le temps.
Oben Sonne

3

paramiko a finalement fonctionné pour moi après avoir ajouté une ligne supplémentaire, qui est vraiment importante (ligne 3):

import paramiko

p = paramiko.SSHClient()
p.set_missing_host_key_policy(paramiko.AutoAddPolicy())   # This script doesn't work for me unless this line is added!
p.connect("server", port=22, username="username", password="password")
stdin, stdout, stderr = p.exec_command("your command")
opt = stdout.readlines()
opt = "".join(opt)
print(opt)

Assurez-vous que le package paramiko est installé. Source originale de la solution: Source


1
#Reading the Host,username,password,port from excel file
import paramiko 
import xlrd

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

loc = ('/Users/harshgow/Documents/PYTHON_WORK/labcred.xlsx')
wo = xlrd.open_workbook(loc)
sheet = wo.sheet_by_index(0)
Host = sheet.cell_value(0,1)
Port = int(sheet.cell_value(3,1))
User = sheet.cell_value(1,1)
Pass = sheet.cell_value(2,1)

def details(Host,Port,User,Pass):
    ssh.connect(Host, Port, User, Pass)
    print('connected to ip ',Host)
    stdin, stdout, stderr = ssh.exec_command("")
    stdin.write('xcommand SystemUnit Boot Action: Restart\n')
    print('success')

details(Host,Port,User,Pass)

1

Fonctionne parfaitement...

import paramiko
import time

ssh = paramiko.SSHClient()
#ssh.load_system_host_keys()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect('10.106.104.24', port=22, username='admin', password='')

time.sleep(5)
print('connected')
stdin, stdout, stderr = ssh.exec_command(" ")

def execute():
       stdin.write('xcommand SystemUnit Boot Action: Restart\n')
       print('success')

execute()

1

La réponse acceptée n'a pas fonctionné pour moi, voici ce que j'ai utilisé à la place:

import paramiko
import os

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
# ssh.load_system_host_keys()
ssh.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
ssh.connect("d.d.d.d", username="user", password="pass", port=22222)

ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -alrt")
exit_code = ssh_stdout.channel.recv_exit_status() # handles async exit error 

for line in ssh_stdout:
    print(line.strip())

total 44
-rw-r--r--.  1 root root  129 Dec 28  2013 .tcshrc
-rw-r--r--.  1 root root  100 Dec 28  2013 .cshrc
-rw-r--r--.  1 root root  176 Dec 28  2013 .bashrc
...

Vous pouvez également utiliser sshpass :

import subprocess
cmd = """ sshpass -p "myPas$" ssh user@d.d.d.d -p 22222 'my command; exit' """
print( subprocess.getoutput(cmd) )

Références:
1. https://github.com/onyxfish/relay/issues/11
2. https://stackoverflow.com/a/61016663/797495


0

Jetez un œil à spurplus, un wrapper que nous avons développé autour spurqui fournit des annotations de type et quelques gadgets mineurs (reconnexion SFTP, md5, etc. ): https://pypi.org/project/spurplus/


1
un wrapper autour d'un wrapper autour d'un wrapper .. nice :)
Alex R

Eh bien, si vous voulez garder vos scripts de déploiement triviaux, je doute que les outils sous-jacents soient assez simples / abstraits. Jusqu'à présent, nous n'avons pas observé d'abstractions qui fuyaient.
marko.ristin

0

Demander à l'utilisateur d'entrer la commande en fonction de l'appareil
auquel il se connecte. Le code ci-dessous est validé par PEP8online.com.

import paramiko
import xlrd
import time

ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
loc = ('/Users/harshgow/Documents/PYTHON_WORK/labcred.xlsx')
wo = xlrd.open_workbook(loc)
sheet = wo.sheet_by_index(0)
Host = sheet.cell_value(0, 1)
Port = int(sheet.cell_value(3, 1))
User = sheet.cell_value(1, 1)
Pass = sheet.cell_value(2, 1)

def details(Host, Port, User, Pass):
    time.sleep(2)
    ssh.connect(Host, Port, User, Pass)
    print('connected to ip ', Host)
    stdin, stdout, stderr = ssh.exec_command("")
    x = input('Enter the command:')
    stdin.write(x)
    stdin.write('\n')
    print('success')

details(Host, Port, User, Pass)

0

Exemple ci-dessous, dans le cas où vous voulez des entrées utilisateur pour le nom d'hôte, le nom d'utilisateur, le mot de passe et le numéro de port.

  import paramiko

  ssh = paramiko.SSHClient()

  ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())



  def details():

  Host = input("Enter the Hostname: ")

  Port = input("Enter the Port: ")

  User = input("Enter the Username: ")

  Pass = input("Enter the Password: ")

  ssh.connect(Host, Port, User, Pass, timeout=2)

  print('connected')

  stdin, stdout, stderr = ssh.exec_command("")

  stdin.write('xcommand SystemUnit Boot Action: Restart\n')

  print('success')

  details()

5
Plutôt que de republier votre réponse d'il y a deux jours avec un meilleur formatage, pourquoi ne pas modifier l'ancienne réponse et améliorer sa mise en forme?
snakecharmerb
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.