ftplib checking if a file is a folder?
Asked Answered
H

16

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?

Helicograph answered 6/7, 2009 at 17:49 Comment(0)
R
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")
Reciprocate answered 11/10, 2016 at 15:1 Comment(0)
G
6

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.

Gaikwar answered 31/12, 2009 at 14:28 Comment(1)
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.Acidfast
P
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.

Prototrophic answered 12/12, 2011 at 12:51 Comment(1)
Thanks, this is the correct answer. rfc3659 specifies the exact format of the response, so this can be reliably machine-parsed.Hotchkiss
S
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)
Sophiesophism answered 6/7, 2009 at 17:55 Comment(4)
But the output is not standard, so its useless.Helicograph
well, parse it with a different function then. surely there isn't as many types of responses.Sophiesophism
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.Helicograph
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.Sophiesophism
B
4
def is_file(filename):
    return ftp.size(filename) is not None

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

Belda answered 6/7, 2009 at 18:1 Comment(3)
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.Helicograph
@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.Belda
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.Helicograph
B
3
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.

Belda answered 6/7, 2009 at 18:49 Comment(1)
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"?Hengel
C
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!"
Chris answered 18/5, 2012 at 19:52 Comment(1)
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.Chris
K
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
Kennedy answered 24/5, 2013 at 7:24 Comment(0)
R
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.

Runthrough answered 5/12, 2014 at 17:29 Comment(0)
H
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
Hengel answered 27/2, 2017 at 20:2 Comment(0)
A
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'
Antiparallel answered 26/7, 2017 at 7:17 Comment(0)
L
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
Lanalanae answered 20/9, 2017 at 3:6 Comment(0)
A
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.
Aguie answered 15/8, 2019 at 4:4 Comment(0)
D
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
Dibru answered 2/1, 2020 at 8:37 Comment(0)
I
0

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

Immodest answered 9/10, 2022 at 13:59 Comment(0)
S
-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()
Separate answered 25/1, 2015 at 20:37 Comment(1)
Presumption that all files have extension is wrong.Venue

© 2022 - 2024 — McMap. All rights reserved.