1
votes

I am trying to make a python script that automatically moves files from my internal drive to any usb drive that is plugged in. However this destination path is unpredictable because I am not using the same usb drives everytime. With Raspbian Buster full version, the best I can do so far is automount into /media/pi/xxxxx, where that xxxxxx part is unpredictable. I am trying to make my script account for that. I can get the drive mounting points with

drives = os.listdir("/media/pi/")

but I am worried some will be invalid because of not being unmounted before they're yanked out (I need to run this w/o a monitor or keyboard or VNC or any user input other than replacing USB drives). So I'd think I'd need to do a series of try catch statements perhaps in an if elif elif elif chain, to make sure that the destination is truly valid, but I don't know how to do that w/o hardcoding the names in. The only way I know how to iterate thru a set of names I don't know is

for candidate_drive in drives:

but I don't know how to make it go onto the next candidate drive only if the current one is throwing an exception.

System: Raspberry Pi 4, Raspbian buster full, Python 3.7.

Side note: I am also trying this on Buster lite w/ usbmount, which does have predictable mounting names, but I can't get exfat and ntfs to mount and that is question for another post.

Update: I was thinking about this more and maybe I need a try, except, else statement where the except is pas and the else is break? I am trying it out.

Update2: I rethought my problem and maybe instead of looking for the exception to determine when to try the next possible drive, perhaps I could instead look for a successful transfer and break the loop if so.

import os
import shutil

files_to_send = os.listdir("/home/outgoing_files/")
source_path = "/home/outgoing_files/" 
possible_USB_drives = os.listdir("/media/")

for a_possible_drive in possible_USB_drives:
    try:
        destpath = os.path.join("/media/", a_possible_drive)           
        for a_file in files_to_send:
            shutil.copy(source_path + a_file, destpath)
    except:
        pass # transfer to possible drive didn't work
    else:
        break # Stops this entire program bc the transfer worked!
1
I'm not familiar with the USB issue you're having, but this sounds like something a generator function might help with.Pokebab
My use of the else block to break the for loop seems to work.rfii

1 Answers

1
votes

If you have an unsigned number of directories in side of a directory, etc... You cannot use nested for cicles. You need to implement a recursive call function. If you have directories inside a directory, you would like to review all the directories, this is posible iterating over the list of directories using a same function that iterate over them, over and over, until it founds the file.

Lets see an example, you have a path structure like this:

path
    dir0
        dir2
            file3
    dir1
        file2
    file0
    file1

You have no way to now how many for cicles are required to iterate over al elements in this structure. You can call an iteration (for cicle) over all elements of a single directory, and do the same to the elements inside that elements. In this structure dirN where N is a number means a directory, and fileN means a file.

You can use os.listdir() function to get the contents of a directory:

print(os.listdir("path/")) 

returns:

['dir0', 'dir1', 'file0.txt', 'file1.txt']

Using this function with all the directories you can get all the elements of the structure. You only need to iterate over all the directories. Specificly directories, because if you use a file:

print(os.listdir("path/file0.txt"))

you get an error:

NotADirectoryError: [WinError 267]

But, remember that in Python exists the generator expressions.

String work

If you have a mainpath you need to get access to a a directory inside this with a full string reference: "path/dirN". You cannot access directly to the file that does not is in the scope of the .py script:

print(os.listdir("dir0/"))

gets an error

FileNotFoundError: [WinError 3]

So you need to always format the initial mainpath with the actual path, in this way you can get access to al the elements of the structure.

Filtering

I said that you could use an generator expression to get just the directories of the structure, recursively. Lets take a look to a function:

def searchfile(paths: list,search: str):
    for path in paths:
        print("Actual path: {}".format(path))

        contents = os.listdir(path)
        print("Contents: {}".format(contents))
   
        dirs = ["{}{}/".format(path,x) for x in contents if os.path.isdir("{}/{}/".format(path,x)) == True]
        print("Directories: {} \n".format(dirs))

        searchfile(dirs,search)

In this function we are getting the contents of the actual path, with os.listdir() and then filtering it with a generator expression. Obviusly we use recursive function call with the dirs of the actual path: searchfile(dirs,search)

This algorithm can be applied to any file structure, because the path argument is a list. So you can iterate over directories with directories with directories, and that directories with more directories inside of them.

If you want to get an specific file you could use the second argument, search. Also you can implement a conditional and get the specific path of the file found:

if search in contents:
    print("File found! \n")
    print("{}".format(os.path.abspath(search)))
    sys.exit()

I hope have helped you.