Python Programming Time, written by Mingo

When writing some scripts in Python, in some cases we need to log in to the remote service frequently to execute a command and return some results.

In a shell environment, this is how we do it.

$ sshpass -p ${passwd} ssh -p ${port} -l ${user} -o StrictHostKeyChecking=no xx.xx.xx.xx "ls -l"
Copy the code

Then you’ll find that your output contains a lot of information that you don’t need, but can’t get rid of (there may be a way, please leave a comment), and so on

host: xx.xx.xx.xx, port: xx
Warning: Permanently added '[xx.xx.xx.xx]:xx' (RSA) to the list of known hosts.
Login failure: [Errno 1] This server is not registered to rmp platform, please confirm whether cdn server.
total 4
-rw-r--r-- 1 root root 239 Mar 30  2018 admin-openrc
Copy the code

For those who execute commands directly using shell commands, they can either pipe directly or redirect the standard output to a file to get the result returned by executing the command

1. Use the subprocess

When doing this in Python, the first thing that usually comes to mind is to use os.popen, os.system, Commands, subprocess, and other command execution libraries to do it indirectly.

But as far as I know, these libraries take outputs that not only contain standard outputs, but also standard errors (that is, the excess information above)

Therefore, we need to clean the data of output every time and then organize and format it to get the data we want.

Use subprocesses, for example, like this

import subprocess
ssh_cmd = "sshpass -p ${passwd} ssh -p 22 -l root -o StrictHostKeyChecking=no xx.xx.xx.xx 'ls -l'"
status, output = subprocess.getstatusoutput(ssh_cmd)

# Data cleaning, formatted will not be displayed<code... >Copy the code

Through the above text + code display, you can feel the SSH login several pain points

  • Pain point 1: Additional installation of SSHPass (if unavoidable)
  • Pain point 2: too much interference information, data cleaning, formatting is quite troublesome
  • Pain point 3: The code implementation is not elegant (a bit tacky) and the readability is poor
  • Pain point 4: SSH connections cannot be reused and can be executed only once
  • Pain point 5: The code is not platform-wide, it only works on Linux and OSX

To address these issues, I searched the web for articles on Python SSH, and didn’t see any that covered the entire technique.

To do so, I checked out a popular Github project: awesome-python-cn (github.com/BingmingWon…

Expect to find some useful libraries on remote connections here.

I did find two of them

  • sh.ssh
  • Paramiko

2. Use sh. SSH

Let’s start with the first one, sh.ssh

Sh is a library that allows you to complete Linxu/OSX system commands by calling functions.

$ python3 -m pip install sh
Copy the code

Only one of its functions will be introduced today: SSH

Usually two machines access each other, for convenience, you can set up a secret login, so you do not need to enter a password.

This code will implement a secret free login and execute our command ls -l

from sh import ssh
output=ssh("[email protected]"."-p 22"."ls -l")
print(output)
Copy the code

But chances are, we don’t want to set up trust exempt, so for the sake of making this code more generic, I’ll assume that we don’t set up a trust exempt and can only log in using a password.

To enter a password, you have to use an interactive method. How do you do that in Python?

It turns out that the SSH method takes a _out argument, which can be a string representing the file path, a file object (or file-like object), or a callback function that is called to pass the output to when there is standard output.

That makes it easier.

All I have to do is recognize the word “password:” and write my password into standard input.

The complete code is as follows:

import sys
from sh import ssh

aggregated = ""
def ssh_interact(char, stdin):
    global aggregated
    sys.stdout.write(char.encode())
    sys.stdout.flush()
    aggregated += char
    if aggregated.endswith("password: "):
        stdin.put("you_password\n")

output=ssh("[email protected]"."-p 22"."ls -l",_tty_in=True, _out_bufsize=0, _out=ssh_interact)
print(output)
Copy the code

This is the official document (amoffat. Making. IO/sh/tutorial…

When you try to run it, you find that the program runs forever, never returns, never exits, and the callback never enters.

After checking the source code through debugging, I still could not find the problem, so I went to Github to search, it turned out that this problem had already existed in 2017, and it has not been fixed until 2020. It seems that not many people use sh.ssh, so I “asked” again, expecting to get a reply.

The above problem will only occur when the password needs to be entered. If the machine is set up, there is no problem with mutual trust.

To get a feel for sh.ssh, I set my machine to trust no secrets and used the following code.

from sh import ssh

my_server=ssh.bake("[email protected]"."-p 22")

Execute the command once, and exit the login after executing it
print(my_server.ls())

# During sleep, manually log in to the server and use top to see how many terminals are currently connected
time.sleep(5)

# if this command is executed again, the number of logins will be +1, and then it will be -1 again
print(my_server.ifconfig())
Copy the code

It’s amazing to see that with bake, my_server.ls() and my_server.ifconfig() appear to be running the same SSH connection twice, but you can actually see the change of connected terminals on a remote machine by running the top command. +1 and then -1 indicates that two commands are executed through two connections.

In this case, using sh.ssh solves pain points one (if the above issues can be addressed), two, and three.

But it still doesn’t reuse SSH connections, it’s still inconvenient, and it’s not my ideal solution.

On top of that, sh is a module that only supports Linxu/OSX. On Windows you have to use its sister library, PBS. Then I went to Pypi to take a look at PBS and it was “in disrepair” and no one was maintaining it.

At this point, I left “pawn”, just short of the last straw.

3. USES paramiko

With a last ditch hope, I tried the Paramiko library and found the elegance of Python in Paramiko.

You can install it by using the following command

$ python3 -m pip install paramiko
Copy the code

Then, I will introduce several common SSH login methods

Method 1: Use sshClient to log in using the user name and password

You can then use the following code to connect remotely on a Linux/OSX system

import paramiko

ssh = paramiko.SSHClient()
# allow connections to hosts not in the know_hosts file
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())

# establish a connection
ssh.connect("xx.xx.xx.xx", username="root", port=22, password="you_password")

Use this connection to execute the command
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l")

# fetch output
print(ssh_stdout.read())

# close the connection
ssh.close()
Copy the code

Method 2: Use transport to log in using the user name and password

Method 1 is a traditional operation of connecting to the server, executing commands and closing. Multiple operations need to be connected multiple times, so the connection cannot be reused [Pain point 4].

Sometimes you need to log in to the server to perform multiple operations, such as executing commands, uploading/downloading files, but method 1 cannot do this, you can use transport method instead.

import paramiko

# establish a connection
trans = paramiko.Transport(("xx.xx.xx.xx".22))
trans.connect(username="root", password="you_passwd")

Transport of the sshClient object is trans
ssh = paramiko.SSHClient()
ssh._transport = trans

# The rest is the same as above
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh_stdin, ssh_stdout, ssh_stderr = ssh.exec_command("ls -l")
print(ssh_stdout.read())

# close the connection
trans.close()
Copy the code

Method 3: Use the public key SSHClient to log in

import paramiko

# Specify the local RSA private key file
# if there is a password set when creating the key pair, the password is set as the password
pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345')

# establish a connection
ssh = paramiko.SSHClient()
ssh.connect(hostname='xx.xx.xx.xx',
            port=22,
            username='you_username',
            pkey=pkey)

# execute command
stdin, stdout, stderr = ssh.exec_command('ls -l')

The result will be put into stdout, or if there are any errors it will be put into stderr
print(stdout.read())

# close the connection
ssh.close()
Copy the code

Method 4: Use key – based Transport to log in

import paramiko

# Specify the local RSA private key file
# if there is a password set when creating the key pair, the password is set as the password
pkey = paramiko.RSAKey.from_private_key_file('/home/you_username/.ssh/id_rsa', password='12345')

# establish a connection
trans = paramiko.Transport(('xx.xx.xx.xx'.22))
trans.connect(username='you_username', pkey=pkey)

Transport of the sshClient object is trans
ssh = paramiko.SSHClient()
ssh._transport = trans

# Execute command, same as traditional method
stdin, stdout, stderr = ssh.exec_command('df -hl')
print(stdout.read().decode())

# close the connection
trans.close()
Copy the code

The above four methods can help you to remotely log in to the server to execute commands. If you need to reuse the connection, you can use method 2 and method 4 to execute multiple commands at one time

When you’re done, close the connection.

SFTP file transfer is implemented

Also, Paramiko is the perfect solution for SSH. It is very professional and can also be used for SFTP file transfers.

import paramiko

# # to instantiate a trans object instantiated a transport object
trans = paramiko.Transport(('xx.xx.xx.xx'.22))

# establish a connection
trans.connect(username='you_username', password='you_passwd')

Instantiate an SFTP object that specifies the channel to connect to
sftp = paramiko.SFTPClient.from_transport(trans)

# send file
sftp.put(localpath='/tmp/11.txt', remotepath='/tmp/22.txt')

# Download file
sftp.get(remotepath='/tmp/22.txt', localpath='/tmp/33.txt')
trans.close()
Copy the code

At this point, Paramiko has won, but there’s still one pain point we haven’t mentioned, which is multi-platform, which is Windows, which is a good thing and a bad thing.

The good news: Paramiko supports Windows

The bad thing is, you need to do a lot of complicated preparation, you can Google, but I suggest you just give up, the pit is too deep.

4. Write at the end

After some comparisons and examples, Paramiko is a professional and relaxing SSH tool. In my opinion, the Paramiko module is one of the required modules for operation and maintenance personnel. If you happen to need to implement SSH in Python code to get some information from a remote server, Then I recommend Paramiko to you.

Finally, I hope this article can help you.

5. Reference links

  • Github.com/paramiko/pa…
  • docs.paramiko.org
  • www.liujiangblog.com/blog/15/

This article was automatically published by ArtiPub