20

How can i check if a file on a remote ftp is a folder or not using ftplib?

Best way i have right now is to do a nlst, and iterate through calling size on each of the files, if the file errors out then it is a folder?

Is there a better way? I cannot parse the output of list, since there is about a dozen different ftp servers(many extremely old.)

What should i do?

UberJumper
  • 20,245
  • 19
  • 69
  • 87

16 Answers16

10

There are not "isdir" and "isfile" definitions in ftplib. If you don't have to use ftplib, i recommend you to use ftputil.

First of all you have to install ftputil package. To achive this, use this command: python -m pip install ftputil. After the installation, you can import the library to your code. I think it's enough explanation. So, let's go to the implementation:

import ftputil
with ftputil.FTPHost("host", "username", "password") as ftp_host:
    ftp_host.chdir("/directory/")   
    list = ftp_host.listdir(ftp_host.curdir)
    for fname in list:
        if ftp_host.path.isdir(fname):
            print(fname + " is a directory")
        else:
            print(fname + " is not a directory")
Ceylan B.
  • 564
  • 9
  • 23
7

FTP is quite a rudimentary protocol and there's no built-in protocol query allowing you to get the type (file, dir) of each node, so a heuristic like the one you found is the only solution.

If getting the size of each node doesn't work, perhaps you should consider calling FTP.nlst() on each of those nodes: those that error out will be files rather than dirs.

Matt Joiner
  • 112,946
  • 110
  • 377
  • 526
Antoine P.
  • 4,181
  • 1
  • 24
  • 17
  • Might be because of the server but when I tried this nlst() doesn't error on files. When called on a file it returned a list of length 1 containing the filename so I was able to look at that to determine if it was a file or dir. – dgundersen Apr 20 '18 at 12:37
6

You can use MLST command:

import ftplib
f = ftplib.FTP()
f.connect("localhost")
f.login()
print f.sendcmd('MLST /')

Against pyftpdlib server the code above prints:

250-Listing "/":
 modify=20111212124834;perm=el;size=12288;type=dir;unique=807g100001; /
250 End MLST.

What you have to do is parse that string and look for "type=dir" or "type=cdir" (current dir, as in ".") or "type=pdir" (parent dir as in "..") via a regular expression. If you get a match, it means that the provided path refers to a directory.

Giampaolo Rodolà
  • 12,488
  • 6
  • 68
  • 60
  • Thanks, this is the correct answer. [rfc3659](https://tools.ietf.org/html/rfc3659#page-23) specifies the exact format of the response, so this can be reliably machine-parsed. – hasufell May 25 '16 at 22:36
4

FTP.dir returns a directory listing, that you can parse with a callback function to find out whether it's a directory. For example, like this:

def parse(line):
    if line[0] == 'd':
        print(line.rpartition(' ')[2])   # gives you the name of a directory

ftp.dir(parse)
SilentGhost
  • 307,395
  • 66
  • 306
  • 293
  • 2
    But the output is not standard, so its useless. – UberJumper Jul 06 '09 at 18:16
  • well, parse it with a different function then. surely there isn't as many types of responses. – SilentGhost Jul 06 '09 at 18:19
  • 1
    Well considering right now at count there is 57 different remote ftp servers, some running linux/windows/HPUX/solaris/BSD ftp clients, + the random plethora of 3rd party ftp clients. Its a pain. – UberJumper Jul 06 '09 at 18:28
  • why do you think that there will be as many response formats? as long as some form of drwxrwxrwx is there, you can split string and use regexp or other technique to find out what field contains permission. – SilentGhost Jul 06 '09 at 18:36
4
def is_file(filename):
    return ftp.size(filename) is not None

This works because ftp.size returns None if it is a directory.

Evan Fosmark
  • 98,895
  • 36
  • 105
  • 117
  • The problem is i just noticed that one of the ftp servers, actually returns the filesize of a folder. Which shouldnt happen, but it does. Jolly. – UberJumper Jul 06 '09 at 18:13
  • 1
    @uberjumper, I have one more idea to try that I'll post soon. That's really surprising that it returns the folder size. @SilentGhost, I did it in 2.6, but thanks for the note. If I share it with someone using 3.x I'll be sure to give them a modified version. – Evan Fosmark Jul 06 '09 at 18:23
  • Yes i know, also it returns the MDTM date also. I've seen one or two other ftp servers that dump folder sizes, but don't dump file modification dates. I eagrly await your solution. – UberJumper Jul 06 '09 at 18:30
2
def is_file(filename):
    current = ftp.pwd()
    try:
        ftp.cwd(filename)
    except:
        ftp.cwd(current)
        return True
    ftp.cwd(current)
    return False

Here is another solution. Half way writing it out I realized it has a problem. If you don't have permission to change to a folder, it'll read it as a file instead. It'll work if you have access to any folder.

I still posted it because maybe it'll give some ideas.

Evan Fosmark
  • 98,895
  • 36
  • 105
  • 117
  • I solved the issue by checking if the error it throws contains "550 Access is Denied". This works for the server to which I am connected, but I am doubtful that all servers will respond in this fashion. Perhaps a dictionary of known 550 error messages can be used? Or just check if it says "550"? – Nick Feb 27 '17 at 19:58
1

I have used this when dealing with ftplib:

import ftplib
from ftplib import FTP
ftp=FTP("ftp address")
ftp.login("user","password")

path = "/is/this/a/file/or/a/folder/"

try:
    ftp.cwd(path)
    print "it's a folder!"
except ftplib.error_perm:
    print "it's not a folder!"
jameh
  • 1,149
  • 3
  • 12
  • 20
  • 2
    like @Evan Fosmark said, if user does not have suff. permissions to cwd a given folder, this code will identify that folder as a file. – jameh May 18 '12 at 19:56
1

This is what I use for finding the directories in a given directory. I return the directory names as a set because I use sets later on in the program, but you could leave off the set creation at the end and return the directory names as a list, tuple, etc.

def get_directories(ftp_server):
    """
    Returns a set of directories in the current directory on the FTP server

    Stdout output of ftp_server.dir() is redirected to an IO object and then
    reset, because ftp_server.dir() only prints its results to stdout.

    @param ftp_server: Open connection to FTP server
    @return: Set of directory names
    """
    # Redirect stdout
    old_stdout = sys.stdout
    sys.stdout = new_stdout = StringIO()
    # Get directory listing
    ftp_server.dir()
    # Reset stdout
    sys.stdout = old_stdout

    directory_listing = new_stdout.getvalue().split("\n")
    # Directories are listed starting with "d" for "directory"
    only_directories = (x for x in directory_listing if x.startswith("d"))
    # This only deals with directory names without spaces.
    directory_names = set(dirname.split()[-1] for dirname in only_directories)
    return directory_names
Daniel Lee
  • 2,030
  • 1
  • 23
  • 29
0

I have found a solution, but it may not be the best and I think it can be helpful for you.

> def isfile(remote_name):
>     try:
>         ftp.sendcmd('MDTM ' + remote_name)
>     except:
>         return False
>     else:
>         return True

This function will return TRUE if 'remote_name' is a regular file, otherwise will return False.

Diego Vélez
  • 635
  • 6
  • 4
0

My entire solution looks like this:

def is_file(filename):
    try:
        ftp.cwd(filename)
    except Exception as e:
        if "550" in e:
            return True
    return False
Nick
  • 315
  • 2
  • 9
0
ftp_conn = FTP(host=host_name, user=user_name, passwd=password, acct='', timeout=None, source_address=None)
ftp_conn.cwd(path_ftp)
for data_dir in ftp_conn.mlsd():
    print(data_dir)

Output:

('3', {'modify': '20170719134928', 'perm': 'flcdmpe', 'type': 'dir', 'unique': '804U485826E', 'unix.group': '10000', 'unix.mode': '0755', 'unix.owner': '10754'})

see 'type': 'dir'
Dov Benyomin Sohacheski
  • 7,133
  • 7
  • 38
  • 64
rtut
  • 303
  • 1
  • 3
  • 18
0
def _get_file_type(self, path):
    """
Returns string parsed from MLST command
    """
    try:
        return_str = self.ftp.sendcmd('MLST ' + path)
    except BaseException as e:
        raise BaseException("No such file or directory")
    c = re.search('Type=(dir|file);', return_str)
    try:
        filetype = c.group(1)
    except AttributeError as e:
        raise BaseException("Unable to parse")
    return filetype


def is_file(self,path):
    """
Returns if a filepath is a file
    """
    filetype = self._get_file_type(path)
    if filetype == 'file':
        return True
    return False
jmstoh
  • 157
  • 1
  • 9
0

This function returns True if object is a file, otherwise returns False.

def _is_file(FTP, object):
    if FTP.nlst(object) == [object]:
        return True
    else:
        return False

Usage example:

import ftplib

with ftplib.FTP(host=ftp_link, user=ftp_username, passwd=ftp_password) as FTP:
    if _is_file(FTP, object='/path/filename.extention'):
        print(object, 'is file!')
    else:
        print(object, 'is directory!')

How does FTP.nlst() work?

  1. FTP.nlst(object) returns [object] if the object is a file.
  2. FTP.nlst(object) returns an empty list if the object is an empty folder.
  3. FTP.nlst(object) returns a list of strings if the object is a folder with files or directories.
Comrade Che
  • 619
  • 8
  • 25
0

If you want check if a path belongs to a file or directory, the best solution is what you mentioned already:

ftp = FTP()
ftp.connect(host, port)
ftp.login(user, pass)

# returns True if the path belongs to a file and False if it's a folder
def is_file(path):
    try:
        ftp.size(path)
        return True
    except:
        return False

But if you want to get list of files or folders inside a folder, you can use mlsd method. This method returns a dictionary which its keys are names of sub-elements and its values are another dictionary showing attributes of each element, in each value, there's a key named type which shows type of the element:

cdir: current(!?) directory (It belongs to . key)

pdir: parent directory (It belongs to .. key)

dir: directory.

file: file!

So, for example this function returns the a tuple containing list of files and list of directories:

def ls(path):
    files_list = []
    directories_list = []
    for k, v in ftp.mlsd(path):
        if v['type'] == 'file':
            files_list.append(k)
        elif v['type'] == 'dir':
            directories_list.append(k)
    return files_list, directories_list
Hamidreza
  • 1,465
  • 12
  • 31
0

Use dir() method. It shows type of content as number. 1 is file and 2 is folder. enter image description here

Javad
  • 128
  • 8
-4

All files have an extension, so by using split('.') it can be broken into a list of size 2. While for directories the size of list will be one after using split('.'). So by checking size of list we can identify if its a file or a directory.

os.chdir(r"path")
ftp = ftplib.FTP('ftp.some.server')
ftp.login('username','password')
filelist=ftp.nlst()
for file in filelist:
    fildir=file.split('.')
    filsize=len(fildir)
    if(filsize>1):
        ftp.retrbinary('RETR %s' %file, open(file, 'wb').write)
ftp.close()