SFTP in Python: Really Simple SSH

ssh.py provides three common SSH operations, get, put and execute. It is a high-level abstraction upon Paramiko.

I wrote it yesterday for my own needs, so it is still very much in the beta stage. Any improvements or comments gratefully accepted.

In short, it works as follows:

import ssh
s = ssh.Connection('example.com')
s.put('hello.txt')
s.get('goodbye.txt')
s.execute('du -h --max-depth=0')
s.close()

That is it, in the rest of this post, I walk through this line by line.

Installation

First, we need to install paramiko, if you don't have it already.

On Gentoo Linux:

emerge paramiko

On Ubuntu/Debian and so on:

apt-get install python-paramiko

If you want to use Python's easy_install then:

easy_install paramiko

Secondly, you need to grab the ssh.py module, grab it from my code-page, and save it as ssh.py.

Connecting to a remote server

To play with the script interactively, you need to start Python:

python

Now, import the ssh module:

import ssh

Next we need to initiate the connection. If your username is the same on both systems, and you have set up ssh-keys, then all you need to do is:

s = ssh.Connection('example.com')

Connection supports the following options:

host The Hostname of the remote machine.
username Your username at the remote machine.
private_key Your private key file.
password Your password at the remote machine.
port The SSH port of the remote machine.

The host is essential of course. Port defaults to 22. The username defaults to the username you are currently using on the local machine.

You need to use one of the authentication methods, a private key or a password. If you don't specify anything, then ssh.Connection will attempt to use a private_key at ~/.ssh/id_rsa or ~/.ssh/id_dsa.

So to specify a username and password, you can do it like this:

s = ssh.Connection(host = 'example.com', username = 'warrior', password = 'lennalenna')

Of course, Python also allows you to use the order to specify the arguments, so the last example can be written as:

s = ssh.Connection('example.com', 'warrior', password = 'lennalenna')

Operations

Once you have set up the connection, there are three methods you can use. Firstly, to send a file from the local machine, you can use put:

s.put('hello.txt')

The above example copies a file called hello.txt from the current local working directory to the remote server. We can also be more explicit if we want:

s.put('/home/warrior/hello.txt', '/home/zombie/textfiles/report.txt')

So the above example copies /home/warrior/hello.txt on the local server to /home/zombie/textfiles/report.txt on the remote server.

The second operation works in a similar way but in reverse:

s.get('hello.txt')

get takes the file from the remote server to the local server, again we can be more explicit if we want:

s.get('/var/log/strange.log', '/home/warrior/serverlog.txt')

The above example copies the strange.log from the server and saves it as serverlog.txt.

The last operation is execute, this executes a command on the remote server:

s.execute('ls -l')

This returns the output as a Python list.

Closing the connection

You can do as many operations you like while the connection is open, but when you are finished, you need to close the connection between the local and remote machines. You do this with the close method:

s.close()

There we go, that is all I needed to do with SSH. Please do let me know using the comments below if you have any problems using it.

If you import my module in your program and later find that you need more power or flexibility, you should be able to swap it out for the full paramiko with a minimum of fuss.

22 thoughts on “SFTP in Python: Really Simple SSH

  1. <p>Awesome. Paramiko is over complicated for my simple single SSH command needs. I have usually just resorted to using a system command and reading from that.</p>

  2. <p>This is pretty nice, maybe if an mget or mput could be added it would really improve its use. not sure how to do that couldn't figure it out using paramiko its probably impossible as of now. But i just learned python a week ago lol. This program really simplifies paramiko, good job! props</p>

  3. <p>Thanx for coding this up... One glitch I found:</p>
    <blockquote>
    #key_key = paramiko.RSAKey.from_private_key_file(private_key_file)
    key_key = paramiko.DSSKey.from_private_key_file(private_key_file)
    self._transport.connect(username = username, pkey = key_key)</blockquote>
    <p>i'm resorting to this for the time being... it really should be conditionalized to choose the right RSA/DSS based on the private_key_file name, but well, thought you should know</p>

  4. <p>I too ran into some issues with your code, but I've modified it account for RSA and DSA/DSS keys. I'd post it but whatever commenting software is in use here is irking me. Basically do a check in your 'Try to use default key' block and set the rsa_key var to paramiko.RSAKey.from_private_key_file if its the former, or paramoki.DSSKey.from_private_key_file if its the latter.</p>

  5. <p>It's working for me but it's slow, as it's not working with the ssh trick to re-use opened socket. Probably a paramiko problem, or the right flag not given to paramiko ?</p>
    <dl class="docutils">
    <dt>(this is the trick)</dt>
    <dd><dl class="first last docutils">
    <dt>Host *</dt>
    <dd>ControlMaster auto
    ControlPath <a class="reference external" href="mailto:/tmp/%r&#64;%h">/tmp/%r&#64;%h</a>:%p</dd>
    </dl>
    </dd>
    </dl>

  6. <p>I got this stacktrace:</p>
    <div class="highlight"><pre> <span class="o">&gt;&gt;&gt;</span> <span class="n">username</span> <span class="o">=</span> <span class="s">&#39;user&#39;</span>
    <span class="o">&gt;&gt;&gt;</span> <span class="n">s</span> <span class="o">=</span> <span class="n">ssh</span><span class="o">.</span><span class="n">Connection</span><span class="p">(</span><span class="s">&#39;<a href="http://example.com" rel="nofollow">example.com</a>&#39;</span><span class="p">,</span><span class="n">username</span><span class="p">)</span>
    <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
    <span class="n">File</span> <span class="s">&quot;&lt;stdin&gt;&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
    <span class="n">File</span> <span class="s">&quot;ssh.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">46</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__init__</span>
    <span class="n">rsa_key</span> <span class="o">=</span> <span class="n">paramiko</span><span class="o">.</span><span class="n">RSAKey</span><span class="o">.</span><span class="n">from_private_key_file</span><span class="p">(</span><span class="n">private_key_file</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/pkey.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">197</span><span class="p">,</span> <span class="ow">in</span> <span class="n">from_private_key_file</span>
    <span class="n">key</span> <span class="o">=</span> <span class="n">cls</span><span class="p">(</span><span class="n">filename</span><span class="o">=</span><span class="n">filename</span><span class="p">,</span> <span class="n">password</span><span class="o">=</span><span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/rsakey.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">51</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__init__</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">_from_private_key_file</span><span class="p">(</span><span class="n">filename</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/rsakey.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">164</span><span class="p">,</span> <span class="ow">in</span> <span class="n">_from_private_key_file</span>
    <span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_read_private_key_file</span><span class="p">(</span><span class="s">&#39;RSA&#39;</span><span class="p">,</span> <span class="n">filename</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/pkey.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">279</span><span class="p">,</span> <span class="ow">in</span> <span class="n">_read_private_key_file</span>
    <span class="n">data</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_read_private_key</span><span class="p">(</span><span class="n">tag</span><span class="p">,</span> <span class="n">f</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/pkey.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">322</span><span class="p">,</span> <span class="ow">in</span> <span class="n">_read_private_key</span>
    <span class="k">raise</span> <span class="n">PasswordRequiredException</span><span class="p">(</span><span class="s">&#39;Private key file is encrypted&#39;</span><span class="p">)</span>
    <span class="n">paramiko</span><span class="o">.</span><span class="n">PasswordRequiredException</span><span class="p">:</span> <span class="n">Private</span> <span class="n">key</span> <span class="nb">file</span> <span class="ow">is</span> <span class="n">encrypted</span>
    </pre></div>
    <p>and this:</p>
    <div class="highlight"><pre><span class="o">&gt;&gt;&gt;</span> <span class="n">s</span> <span class="o">=</span> <span class="n">ssh</span><span class="o">.</span><span class="n">Connection</span><span class="p">(</span><span class="s">&#39;<a href="http://example.com" rel="nofollow">example.com</a>&#39;</span><span class="p">,</span><span class="n">username</span><span class="p">,</span><span class="s">&#39;~/.ssh/id_rsa&#39;</span><span class="p">,</span><span class="s">&#39;password&#39;</span><span class="p">)</span>
    <span class="n">Traceback</span> <span class="p">(</span><span class="n">most</span> <span class="n">recent</span> <span class="n">call</span> <span class="n">last</span><span class="p">):</span>
    <span class="n">File</span> <span class="s">&quot;&lt;stdin&gt;&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">1</span><span class="p">,</span> <span class="ow">in</span> <span class="o">&lt;</span><span class="n">module</span><span class="o">&gt;</span>
    <span class="n">File</span> <span class="s">&quot;ssh.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">33</span><span class="p">,</span> <span class="ow">in</span> <span class="n">__init__</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">_transport</span><span class="o">.</span><span class="n">connect</span><span class="p">(</span><span class="n">username</span> <span class="o">=</span> <span class="n">username</span><span class="p">,</span> <span class="n">password</span> <span class="o">=</span> <span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/transport.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">855</span><span class="p">,</span> <span class="ow">in</span> <span class="n">connect</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">auth_password</span><span class="p">(</span><span class="n">username</span><span class="p">,</span> <span class="n">password</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/transport.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">1016</span><span class="p">,</span> <span class="ow">in</span> <span class="n">auth_password</span>
    <span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">auth_handler</span><span class="o">.</span><span class="n">wait_for_response</span><span class="p">(</span><span class="n">my_event</span><span class="p">)</span>
    <span class="n">File</span> <span class="s">&quot;/var/lib/python-support/python2.5/paramiko/auth_handler.py&quot;</span><span class="p">,</span> <span class="n">line</span> <span class="mf">174</span><span class="p">,</span> <span class="ow">in</span> <span class="n">wait_for_response</span>
    <span class="k">raise</span> <span class="n">e</span>
    <span class="n">paramiko</span><span class="o">.</span><span class="n">BadAuthenticationType</span><span class="p">:</span> <span class="n">Bad</span> <span class="n">authentication</span> <span class="nb">type</span> <span class="p">(</span><span class="n">allowed_types</span><span class="o">=</span><span class="p">[</span><span class="s">&#39;publickey&#39;</span><span class="p">])</span>
    </pre></div>

  7. <p>but i need more help. I'm have to execute the sudo command after I log in. What do I need to do to enter the password after the sudo command is executed?</p>
    <p>Thanks</p>

  8. <p>Nice! Is there anyway to implement a ServerAliveInterval for long processes? This is because my our firewall keeps closing the connection based on inactive connections.</p>
    <p>Thanks,</p>

  9. <p>Hi,</p>
    <p>Thank-you very much for this, its really useful. Here is a function I've added to this to download the contents of one remote directory to a local one</p>
    <div class="highlight"><pre><span class="k">def</span> <span class="nf">getdir</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">remotepath</span><span class="p">,</span> <span class="n">localpath</span> <span class="o">=</span> <span class="bp">None</span><span class="p">):</span>
    <span class="sd">&quot;&quot;&quot;Copies and entire directory from remote to local.</span>

    <span class="sd"> Based on what is seen by listdir.</span>

    <span class="sd"> &quot;&quot;&quot;</span>
    <span class="k">if</span> <span class="ow">not</span> <span class="n">localpath</span><span class="p">:</span>
    <span class="n">localpath</span> <span class="o">=</span> <span class="n">os</span><span class="o">.</span><span class="n">path</span><span class="o">.</span><span class="n">abspath</span><span class="p">(</span><span class="n">os</span><span class="o">.</span><span class="n">curdir</span><span class="p">)</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">_sftp_connect</span><span class="p">()</span>
    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="bp">self</span><span class="o">.</span><span class="n">_sftp</span><span class="o">.</span><span class="n">listdir</span><span class="p">(</span><span class="n">remotepath</span><span class="p">):</span>
    <span class="k">print</span> <span class="s">&quot;recovering &quot;</span><span class="p">,</span> <span class="n">i</span>
    <span class="k">try</span><span class="p">:</span>
    <span class="bp">self</span><span class="o">.</span><span class="n">_sftp</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s">&quot;</span><span class="si">%s</span><span class="s">/</span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">remotepath</span><span class="p">,</span><span class="n">i</span><span class="p">),</span> <span class="s">&quot;</span><span class="si">%s</span><span class="s">/</span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">localpath</span><span class="p">,</span><span class="n">i</span><span class="p">))</span>
    <span class="k">except</span> <span class="ne">IOError</span><span class="p">,</span><span class="n">e</span><span class="p">:</span>
    <span class="k">print</span> <span class="s">&quot;unable to recover item&quot;</span>
    </pre></div>
    <p>Could I make this an easy_install-able project for you? I could set up a quick project for this.</p>
    <p>Thanks.</p>

  10. <p>I've created and easy installable project called zoink-sftp based on your code. I've also extended it to include my getdir. Other additions may follow in the next few days. I've release the project under LGPL to keep it inline with your original code. I've also given you credit for the original work in the file and python eggs. I've also linked back to this page. If you need me to change any details just drop me a line.</p>
    <p>The easy install and bazaar/launchpad details are available here:</p>
    <blockquote>
    <ul class="simple">
    <li><a class="reference external" href="http://www.sourceweaver.com/projects/">http://www.sourceweaver.com/projects/</a></li>
    </ul>
    </blockquote>
    <p>The short and sweet install is:</p>
    <pre class="literal-block">
    easy_install zoink-sftp
    </pre>

  11. <p>Should have posted this here earlier. With permission, I've created a project from Zeth's code. We put it together at our Python User Group meeting as a presentation.</p>
    <blockquote>
    easy_install pysftp</blockquote>
    <p>There are some enhancements too:
    1) automatic support for RSA or DSS keyfiles
    2) support for encrypted keyfiles
    3) ability to disable logging</p>
    <p>There are a few more features planned but nothing surprising, Zeth's module is pretty complete - simple, easy to grok and fills a need.</p>

  12. <p>This is great! Wish I had seen your post last summer when I was trying to get a similar thing to work. I had a tight deadline so I found a workaround, and just a couple weeks ago wrote a module that is pretty similar to yours. If you're interested, email me and I'll send you a copy.</p>

  13. <p>zoink-sftp doesn't install properly.</p>
    <div class="highlight"><pre><span class="n">fd</span> <span class="o">=</span> <span class="nb">file</span><span class="p">(</span><span class="s">&quot;lib/zoinksftp/doc/zoink-sftp.stx&quot;</span><span class="p">)</span>
    <span class="ne">IOError</span><span class="p">:</span> <span class="p">[</span><span class="n">Errno</span> <span class="mf">2</span><span class="p">]</span> <span class="n">No</span> <span class="n">such</span> <span class="nb">file</span> <span class="ow">or</span> <span class="n">directory</span><span class="p">:</span> <span class="s">&#39;lib/zoinksftp/doc/zoink-sftp.stx&#39;</span>
    </pre></div>
    <p>Just as an FYI. pysftp installed in a giffy though.</p>
    <p>I have no affiliations with either party. Just another code wanderer, out and about wondering about the state of command line code. ;-)</p>
    <p>Cya all.</p>

  14. <p>pretty nice,
    but is there a way to get the output of programs running on the remote end before they complete execution?</p>

  15. <p>Hi, nice work, I am having an issue of a command asking for [y/n] to complete the task, is there anyway to do that? Cheers!</p>

  16. <p>Extremely helpful. One comment: the code as it stands doesn't let you do anything about errors that happen on the server. I made the following changes to the execute (and probably should for the put and get methods) so it returns a tuple of (out, err, code).</p>
    <blockquote>
    <dl class="docutils">
    <dt>def execute(self, command):</dt>
    <dd>&quot;&quot;&quot;
    Execute the given commands on a remote machine.
    Returns tuple of stdout, stderr, code
    &quot;&quot;&quot;
    channel = self._transport.open_session()
    channel.exec_command(command)
    stdout = channel.makefile('rb', -1).readlines()
    stderr = channel.makefile_stderr('rb', -1).readlines()
    code = channel.recv_exit_status()
    return stdout, stderr, code</dd>
    </dl>
    </blockquote>

  17. <p>One thing to note, (and this may be obvious to some, but wasn't immediately to me) is you can run multiple commands at once from the execute method, just separate them with a semi-colon. I wanted to write a deploy script to deploy a repo to a live server from my local machine. I did that by doing something like:</p>
    <dl class="docutils">
    <dt>sourcecode:: python</dt>
    <dd>#connection already established
    cmd = []
    cmd.append('cd /home/user/directory/')
    cmd.append('hg pull')
    cmd.append('hg up')
    connection.execute(&quot;; &quot;.join(cmd))</dd>
    </dl>

  18. <p>Thanks for this short tutorial...was auto-FTPing my files from my appserver to webserver for my tech news website. Everything was OK until someone hacked it. Hosting provider is now recommending SFTP. Wish you had covered mput also. Oh well...I will have to do a little more research for that. Thanks again.</p>

How about Global Thermonuclear War? Wouldn't you prefer a good game of chess? Powered by zpress