Get Random File From Folder, Including Subfolders

I found this, but can't get it to do what I want. For starters, I can only get it to return the names of subfolders and not actual files.

What I'm trying to do is create a simple "change desktop images for 2 monitors every x minutes" macro. The only thing missing from Big Sur's built-in randomizer is that it can't go into subfolders, and unfortunately, this is way too full-featured and too hard to figure out. , That's why I'm trying to roll my own.

As a proof of concept, this works if I hardcode a random file:
Screen Shot 2021-11-30 at 2.27.27 PM

So I just need a way to grab a random file from a selected folder and/or its subfolders and feed the path/file name into a variable and I think I'm set.

Can someone help me with this?

That's a clear request and should be easy, but I need a few minutes to remember the parameters for the "ls" command.

Okay I see the following approach: (using "find" instead of "ls" since that seemed easier to remember)

  1. We will need to use the Execute Shell Script action to execute a short Shell script and store the result into a variable. That will be your final result.
  2. We will generate the list of all files with their full paths using the "find /mypath -print" command
  3. We will choose a random item by sorting the results of the above into "sort -R"
  4. We will pick the first one of the above list by piping it into "head -n 1"

I think that should work fine. I'm not sure if you need help putting those pieces together.

Thank you! Not only does your solution work, but it's lightning fast!

You are very welcome.

It's fast for small subdirectories, but if you try it on large subdirectories like the root directory or even your home directory it could take a while.

I'm glad you were able to take my tips and run with it.

Hey Guys,

Just for fun I did this entirely with Keyboard Maestro native actions.

It was much tougher (for me) to get the job done without scripting, but it wasn't ridiculously hard.

-Chris


Return a Random File Image Path from a Directory Stack v1.00.kmmacros (9.0 KB)

@ccstone Thanks! I tried your method too, but for some reason, it's really slow. For around 10k files, it takes well over a minute.

Shell scripts tend to be very fast. They regularly amaze me with their speed.

Zowie! That's atrociously slow...

Here's some AppleScript that'll do the job in less than a second on my old hardware.

--------------------------------------------------------
# Auth: Christopher Stone
# dCre: 2017/03/23 22:04
# dMod: 2021/12/04 20:26
# Appl: AppleScriptObjC
# Task: Recursively extract all file and folder paths from the target folder path.
#     : Return a random file path from the path list.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @BBEdit, @Recursively, @Extract, @File, @Folder, @Paths, @Target, @Folder, @Random
--------------------------------------------------------
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
--------------------------------------------------------

### USER SETTING ###

# Get the POSIX Path of the Front Finder Window
# Replace with your preferred folder path.

tell application "Finder" to set folderToSearchPath to POSIX path of (insertion location as alias)

--------------------------------------------------------

# Convert Tilde-Path to full POSIX Path if needed.
set folderToSearchPath to ((current application's NSString's stringWithString:folderToSearchPath)'s ¬
   stringByExpandingTildeInPath) as text

# Classes, constants, and enums used
set curApp to current application
set NSDirectoryEnumerationSkipsHiddenFiles to a reference to 4
set NSFileManager to a reference to curApp's NSFileManager
set NSDirectoryEnumerationSkipsPackageDescendants to a reference to 2

# Create NSString from Path String
set nsPath to curApp's NSString's stringWithString:folderToSearchPath
# Expand Tilde & Symlink (If Any Exist)
set nsPath to nsPath's stringByResolvingSymlinksInPath()

# Gets the NSURL, Whether or Not the File/Folder Exists
set folderNSURL to curApp's |NSURL|'s fileURLWithPath:nsPath

# Create NSArray of File URLs
set theURLs to (NSFileManager's defaultManager()'s enumeratorAtURL:folderNSURL includingPropertiesForKeys:{} ¬
   options:(NSDirectoryEnumerationSkipsPackageDescendants + ¬
   (get NSDirectoryEnumerationSkipsHiddenFiles)) errorHandler:(missing value))'s allObjects()
# Map NSURLs to their Path Strings and return as a list
set itemPathList to ((theURLs's valueForKey:"path") as list)

set itemPathCount to length of itemPathList
set randomFilePath to item (random number from 1 to itemPathCount) of itemPathList

--------------------------------------------------------

@peternlewis...

I ran my macro from post #5 on a folder with about 12,500 files in it.

It took about 3.5 minutes.

[Edit: The Streamlined version below only takes about 25 seconds.]

On testing it seems the bottleneck is iterating through the path list.

If I do this in AppleScript and then iterate through the list of paths it only takes 0.5 seconds to return the list and about 7 seconds to iterate through them and copy them to another list.

Is it reasonable for Keyboard Maestro to take 3.5 minutes – or have we uncovered a problem?

-Chris

Hey Guys,

Okay, I streamlined the macro as much as I could without scripting and got the time down to ~ 26 seconds on a folder with 12284 items in it.

I used local variables to prevent cruft and/or accidental data accumulation.

I'm disappointed with the speed, but it's waaay better than 3.5 minutes...

-Chris


Return a Random File Image Path from a Directory Stack v1.00.kmmacros (13 KB)

Keyboard Maestro runs actions at 1000 actions per second. So to scan a folder with 10,000 items using a For Each action will take at a minimum 10 seconds per action within the For Each action.

Keyboard Maestro is not designed for high speed use, if you want to process a large number of files, use a script. If you want to scan a folder and find a specific set of subfiles, perhaps use a script that uses the find command. Similarly for lines, use the grep command.

Once you have whittled down the list to a manageable number, then use Keyboard Maestro actions to process them.

1 Like

That seems like a very round number. So I tried it manually on my M1 Mini with the following macro and it reported 1002 actions/sec. That's so close to 1000 we might have to change its name to Kilohertz Maestro.

image

1 Like

FWIW, I stuck with Sleepy's original shell script because of the combination of speed and simplicity. Plus I have it running just once a day because the file listing is saved to a variable and I don't add new images to the folder/subfolders that often, so I can randomize it throughout the day to get new desktop images. Everything's working beautifully.

1 Like