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?

Fredley
la source

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)
shahjapan
la source
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.

powerrox
la source
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.

supersighs
la source
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"])
Michael Williamson
la source
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()
Ronn Macc
la source
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!

IAmSurajBobade
la source
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.

Eric Snow
la source
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

Alexandre Terechkov
la 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)
Harshan Gowda
la source
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()
Harshan Gowda
la source
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 [email protected] -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

CONvid19
la source
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/

marko.ristin
la source
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)
Harshan Gowda
la source
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()
Harshan Gowda
la source
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