49

I am using Python and trying to connect to SFTP and want to retrieve an XML file from there and need to place it in my local system. Below is the code:

import paramiko

sftpURL   =  'sftp.somewebsite.com'
sftpUser  =  'user_name'
sftpPass  =  'password'

ssh = paramiko.SSHClient()
# automatically add keys without requiring human intervention
ssh.set_missing_host_key_policy( paramiko.AutoAddPolicy() )

ssh.connect(sftpURL, username=sftpUser, password=sftpPass)

ftp = ssh.open_sftp()
files = ftp.listdir()
print files

Here connection is success full. And now I want to see all the folders and all the files and need to enter in to required folder for retrieving the XML file from there.

Finally my intention is to view all the folders and files after connecting to SFTP server.

In the above code I had used ftp.listdir() through which I got output as some thing like below

['.bash_logout', '.bash_profile', '.bashrc', '.mozilla', 'testfile_248.xml']

I want to know whether these are the only files present?

And the command I used above is right to view the folders too?

What is the command to view all the folders and files?

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
Shiva Krishna Bavandla
  • 25,548
  • 75
  • 193
  • 313

3 Answers3

48

The SFTPClient.listdir returns everything, files and folders.

Were there folders, to tell them from the files, use SFTPClient.listdir_attr instead. It returns a collection of SFTPAttributes.

from stat import S_ISDIR, S_ISREG
sftp = ssh.open_sftp()

for entry in sftp.listdir_attr(remotedir):
    mode = entry.st_mode
    if S_ISDIR(mode):
        print(entry.filename + " is folder")
    elif S_ISREG(mode):
        print(entry.filename + " is file")

The accepted answer by @Oz123 is inefficient. SFTPClient.listdir internally calls SFTPClient.listdir_attr and throws most information away returning file and folder names only. The answer then uselessly and laboriously re-retrieves all that data by calling SFTPClient.lstat for each file.

See also How to fetch sizes of all SFTP files in a directory through Paramiko.


Obligatory warning: Do not use AutoAddPolicy – You are losing a protection against MITM attacks by doing so. For a correct solution, see Paramiko "Unknown Server"

Martin Prikryl
  • 188,800
  • 56
  • 490
  • 992
18

One quick solution is to examine the output of lstat of each object in ftp.listdir().

Here is how you can list all the directories.

>>> for i in ftp.listdir():
...     lstatout=str(ftp.lstat(i)).split()[0]
...     if 'd' in lstatout: print i, 'is a directory'
... 

Files are the opposite search:

>>> for i in ftp.listdir():
...     lstatout=str(ftp.lstat(i)).split()[0]
...     if 'd' not in lstatout: print i, 'is a file'
... 
oz123
  • 27,559
  • 27
  • 125
  • 187
  • 11
    Relying on the stringification behaviour of `SFTPAttributes` is a horrible, horrible hack. Why don't you do it properly, and use `stat.S_ISDIR(lstatout.st_mode)`? – Fake Name Jan 26 '16 at 04:50
  • 2
    @PANDAStack - `ftp.lstat(i)` returns an instance of the class [`SFTPAttributes`](https://github.com/paramiko/paramiko/blob/6978c7a0b8faa683b119de3a0c096be31ac4fdc9/paramiko/sftp_attr.py#L25). The way this answer does this is to depend on how the library has chosen to have the `__repr__` of `SFTPAttributes` *look*, which is a interface designed **entirely** for programmer debugging, and is probably not tested or guaranteed to be constant, even across patch version numbers. – Fake Name Feb 11 '18 at 11:35
  • Basically, doing it this way is equivalent to parsing debugging output from the console. It may work, but it's extremely brittle, and should only be done if you absolutely have to, and there's no other options. In this case, the entire point of the `SFTPAttributes` class is to conveniently encapsulate metadata about a path on the remote host, so not just using it the way it's *supposed* to work is frankly silly. – Fake Name Feb 11 '18 at 11:38
  • 1
    @PANDAStack - `st_mode` is defined by POSIX. Have a look at the [`stat()` man pages](http://man7.org/linux/man-pages/man2/stat.2.html). Also, [this question on SE](https://stackoverflow.com/q/35375084/268006). – Fake Name Feb 14 '18 at 22:09
  • 1
    This is code is inefficient. See [my answer](https://stackoverflow.com/q/12295551/850848#57694519) for the correct solution. – Martin Prikryl Aug 28 '19 at 14:18
4

Here is a solution I have come up with. Based on https://stackoverflow.com/a/59109706 . My solution gives a pretty output.

Update I have modified it slightly to incorporate Martin's suggestions. Now my code is considerably fast compared to my initial version using isdir and listdir

# prefix components:
space =  '    '
branch = '│   '
# pointers:
tee =    '├── '
last =   '└── '

def stringpath(path):
    # just a helper to get string of PosixPath
    return str(path)

from pathlib import Path
from stat import S_ISDIR
def tree_sftp(sftp, path='.', parent='/', prefix=''):
    """
    Loop through files to print it out
    for file in tree_sftp(sftp):
        print(file)
    """
    fullpath = Path(parent, path)
    strpath = stringpath(fullpath)

    dirs = sftp.listdir_attr(strpath)
    pointers = [tee] * (len(dirs) - 1) + [last]
    pdirs = [Path(fullpath, d.filename) for d in dirs]
    sdirs = [stringpath(path) for path in pdirs]

    for pointer, sd, d in zip(pointers, sdirs, dirs):
        yield prefix + pointer + d.filename
        if S_ISDIR(d.st_mode):
            extension = branch if pointer == tee else space
            yield from tree_sftp(sftp, sd, prefix=prefix + extension)

You can try it out like this using pysftp

import pysftp
with pysftp.Connection(HOSTNAME, USERNAME, PASSWORD) as sftp:
    for file in tree_sftp(sftp):
        print(file)

Let me know if if works for you.

  • Third thing is that you should not use Python `Path` class on SFTP paths. The `Path` uses local file system conventions that might not match the SFTP conventions. Particularly on Windows, your code might fail. – Martin Prikryl Mar 24 '21 at 12:45
  • The python `Path` is being used only for easy concat of path. I convert that to string to use it with pysftp. I didn't include `stringpath` function, I just noticed. Let me add that. It's just a helper. – scientific_explorer Mar 24 '21 at 12:49
  • And that's it, SFTP always uses forward slashes. While the `Path` will use the local system path separator. If that's not a forward slash (like on Windows), your code will fail. You are introducing the same problem as psftp's `get_r` and `put_r` have. See [Python pysftp put_r does not work on Windows](https://stackoverflow.com/q/58460718/850848). You might want to explicitly use `PosixPath`. – Martin Prikryl Mar 24 '21 at 12:51
  • This is awesome, very well done, great concept :D thank you @scientific_explorer – Nico Brenner Jul 06 '23 at 16:53