List all Folders OR Files in a Given Directory


Edit:

To add context – I’m building a script to recreate an empty folder structure on a new hard drive from a populated one on an old hard drive.


Hey Folks,

I’m wanting to Recursively list the paths of all Folders OR Files in a Given Directory.

The appended script works for Folders and is very fast, but unfortunately it includes packages.

Is there a simple way to filter them out?

--------------------------------------------------------
# Auth: Christopher Stone { Building on work by Nigel Garvey }
# dCre: 2021/07/21 01:01
# dMod: 2021/07/21 01:01 
# Appl: AppleScriptObjC, Finder, BBEdit
# Task: Recursively Return File Paths of All Folders in a Directory.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @BBEdit, @Recursive, @Folders
# Vers: 1.00
--------------------------------------------------------
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
--------------------------------------------------------

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

set currentApp to current application

set nsPath to currentApp's NSString's stringWithString:targetFolderPath
set nsPath to nsPath's stringByResolvingSymlinksInPath()

# GETS THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS 
set folderNSURL to currentApp's |NSURL|'s fileURLWithPath:nsPath

# Get URL of all items in folder, recursively
set IsDirectoryKey to currentApp's NSURLIsDirectoryKey
set theFileManager to currentApp's NSFileManager's defaultManager()
set allURLs to (theFileManager's enumeratorAtURL:folderNSURL includingPropertiesForKeys:{IsDirectoryKey} options:((currentApp's NSDirectoryEnumerationSkipsPackageDescendants) + ((currentApp's NSDirectoryEnumerationSkipsHiddenFiles) as integer)) errorHandler:(missing value))'s allObjects()

# Build an array with URLs to directories
set filteredArray to currentApp's NSMutableArray's new()
repeat with oneURL in allURLs
   set keyValue to end of (oneURL's getResourceValue:(reference) forKey:IsDirectoryKey |error|:(missing value))
   if (keyValue as boolean) then (filteredArray's addObject:oneURL)
end repeat

# Convert URL array to file list
set AppleScript's text item delimiters to linefeed
set filteredArray to (filteredArray as list) as text

bbeditNewDoc(filteredArray, "activate") of me

--------------------------------------------------------
--» HANDLERS
--------------------------------------------------------
on bbeditNewDoc(_text, _activate)
   tell application "BBEdit"
      set newDoc to make new document with properties {text:_text, bounds:{0, 44, 1920, 1200}}
      tell newDoc
         select insertion point before its text
      end tell
      if _activate = true or _activate = 1 or _activate = "activate" then activate
   end tell
end bbeditNewDoc
--------------------------------------------------------

I’m also looking at this:

Filtering folder contents with UTI - #10 by NigelGarvey

It’s far slower, but it seems to work when fed an UTI of public.folder.

Slower is relative – 10+ seconds isn’t bad for the folder I’m testing with.

TIA.

-Chris

There’s no simple filter. Other than @NigelGarvey’s script, you could modify yours like this:

# Get URL of all items in folder, recursively
set IsDirectoryKey to currentApp's NSURLIsDirectoryKey
set IsPackageKey to currentApp's NSURLIsPackageKey
set theFileManager to currentApp's NSFileManager's defaultManager()
set allURLs to (theFileManager's enumeratorAtURL:folderNSURL includingPropertiesForKeys:{IsDirectoryKey, IsPackageKey} options:((currentApp's NSDirectoryEnumerationSkipsPackageDescendants) + ((currentApp's NSDirectoryEnumerationSkipsHiddenFiles) as integer)) errorHandler:(missing value))'s allObjects()

# Build an array with URLs to directories
set filteredArray to currentApp's NSMutableArray's new()
repeat with oneURL in allURLs
	set keyValue to end of (oneURL's getResourceValue:(reference) forKey:IsDirectoryKey |error|:(missing value))
	if (keyValue as boolean) then
		set keyValue to end of (oneURL's getResourceValue:(reference) forKey:IsPackageKey |error|:(missing value))
		if not keyValue as boolean then (filteredArray's addObject:oneURL)
	end if
end repeat
1 Like

Hey Shane,

Thanks.

That code is balking on this line on my Mojave system:

if not keyValue as boolean then (filteredArray’s addObject:oneURL)

image

The value of the previous line is:

{true, «class ocid» id «data optr0000000080EC468DFF7F0000»}

-Chris

Hi Chris.

Shane’s code works fine on my Mojave system when slotted into your script. The only way I can see that error occurring is if you’ve lost the ‘end of’ after ‘set keyValue to’.

1 Like

That was indeed the case — I modified the code after Chris’s post.

(I also posted a message explaining what I had done at the same time, but it seems to have disappeared into the ether.)

1 Like

Hey Guys,

Many thanks!

Shane’s script is working fine now, and it’s waay faster for this purpose then the UTI-based script.

-Chris

1 Like

Hey Chris, could you please post your final script so that we have a complete script in one place?

Thanks.

1 Like

Yes. Enjoy.

--------------------------------------------------------
# Auth: Christopher Stone { Building on work by Nigel Garvey }
# dCre: 2021/07/21 01:01
# dMod: 2021/07/21 16:27
# Appl: AppleScriptObjC, Finder, BBEdit
# Task: Recursively Return File Paths of All Folders in a Directory.
# Libs: None
# Osax: None
# Tags: @Applescript, @Script, @ASObjC, @BBEdit, @Recursive, @Folders
# Vers: 1.01
--------------------------------------------------------
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
--------------------------------------------------------

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

set currentApp to current application

set nsPath to currentApp's NSString's stringWithString:targetFolderPath
set nsPath to nsPath's stringByResolvingSymlinksInPath()

# GETS THE NSURL, WHETHER OR NOT THE FILE/FOLDER EXISTS 
set folderNSURL to currentApp's |NSURL|'s fileURLWithPath:nsPath

# Get URL of all items in folder, recursively
set IsDirectoryKey to currentApp's NSURLIsDirectoryKey
set IsPackageKey to currentApp's NSURLIsPackageKey
set theFileManager to currentApp's NSFileManager's defaultManager()
set allURLs to (theFileManager's enumeratorAtURL:folderNSURL includingPropertiesForKeys:{IsDirectoryKey, IsPackageKey} options:((currentApp's NSDirectoryEnumerationSkipsPackageDescendants) + ((currentApp's NSDirectoryEnumerationSkipsHiddenFiles) as integer)) errorHandler:(missing value))'s allObjects()

# Build an array with URLs to directories
set filteredArray to currentApp's NSMutableArray's new()
repeat with oneURL in allURLs
   set keyValue to end of (oneURL's getResourceValue:(reference) forKey:IsDirectoryKey |error|:(missing value))
   if (keyValue as boolean) then
      set keyValue to end of (oneURL's getResourceValue:(reference) forKey:IsPackageKey |error|:(missing value))
      if not keyValue as boolean then (filteredArray's addObject:oneURL)
   end if
end repeat

# Convert URL array to file list
set AppleScript's text item delimiters to linefeed
set filteredArray to (filteredArray as list) as text

bbeditNewDoc(filteredArray, "activate") of me

--------------------------------------------------------
--» HANDLERS
--------------------------------------------------------
on bbeditNewDoc(_text, _activate)
   tell application "BBEdit"
      set newDoc to make new document with properties {text:_text, bounds:{0, 44, 1920, 1200}}
      tell newDoc
         select insertion point before its text
      end tell
      if _activate = true or _activate = 1 or _activate = "activate" then activate
   end tell
end bbeditNewDoc
--------------------------------------------------------

Thanks, Chris.

So, given this comment:
# Get URL of all items in folder, recursively

I’m wondering if this script first get’s all items in the folder, including files?
If so, would that slow things down if you have 1,000+ files in a folder?

Maybe it can’t be avoided. Just asking to learn.

No.

It uses NSURLIsDirectoryKey to filter out files, but packages are directories and therefore included. They have to be filtered-out piecemeal. Unfortunately.

-Chris

Chris, OK, thanks for the clarification.

Only like to say both me and peavine have done similar things on macscripter.net forum. We decided to use predicate to filter things. And my testing show it was much faster to filter thousands of files.

I got so far to store a output to file (cashing) and later manipulate it to a format I want. If I do know the cashing will not change the manipulation of the output was less 1 seconds for more 10 thousands files.

The manipulation was done in vanilla AppleScript.

Its also worth mentioning that the state of how we store files could infect in a bad way (longer time) to execute a script. In other words its not the script itself that is slow its the structor of the directory we have files.

To test this I use python to take every man pages on my computer to store it in desktop folder. I could search any text tag (contents) in whose files more 14 thousands in less and 3 seconds to display the URL of whose files.

I’m also a big fan of the command searchfs (is not included with macOS) to search the hole computer in less and 2 minutes. And do the same with find its very slow in compare.

Hey Fredrik,

Interesting.

Unfortunately MacScripter.net would appear to be down.

Mark? (@alldritt)

searchfs looks useful, but I’m surprised it can only a take a drive as a search location and cannot be pointed at a specific directory.

-Chris

Hi Chris.

macscripter.net’s OK. it’s macscipter.net that’s unavailable. :slight_smile:

1 Like

Ha! I missed that entirely.

Thanks Nigel.

-Chris

Hey Fredrik,

Would you mind pointing to the correct thread on MacScripter?

You have more than a few posts to sort through…  :sunglasses:

-Chris

First I have very serious problem with my laptop keyboard, sometimes I get nothing when other times I get twice the key press. The keys (r,s,t) are more problem and the other. I’m waiting for Apple’s next Macbook Pro or a device with 32GB memory.

@ccstone if you have Shane’s book about ASObjC you have a good examples.

In this example I use predicate to filter with extention of a file.
ex. if theExts parameters is {""} I will only get directory path (if no files are missing extentions)
ex. if theExts parameters is {“txt”} I will only get files of kind txt
The code also include sorting the list.

If you need other way to filter you could change this line to do what you want.
(“pathExtension.lowercaseString IN %@”, theExts)

I made includingPropertiesForKeys:{} it made my script faster.

A you could see with predicate we do not need a repeat loop and sometimes we could use other approach to filter directory folders.

on searchWithExtension:theExts inPath:thePath
	set theURL to current application's |NSURL|'s fileURLWithPath:thePath
	set manager to current application's NSFileManager's defaultManager()
	set directoryEnumerator to (manager's enumeratorAtURL:theURL includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsPackageDescendants as integer) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)) errorHandler:(missing value))'s allObjects()
	set predicate to current application's NSPredicate's predicateWithFormat_("pathExtension.lowercaseString IN %@", theExts)
	set anArray to (directoryEnumerator's |path|)'s filteredArrayUsingPredicate:predicate
	set theSortList to (anArray's sortedArrayUsingSelector:"compare:") as list
end searchWithExtension:inPath:

This example we use lastPathComponent and its easy to find a duplicate of a file
based on the name not size (meaning the files maybe are not the same).
Other other hand if we have a dialog that ask for a string we could get the path
of a basename very quick. And if we have more and 1 item we could choose the first one. In this example I use predicateWithFormat:argumentArray: method…

on searchWithFilename:theName inPath:thePath
	set theURL to current application's |NSURL|'s fileURLWithPath:thePath
	set manager to current application's NSFileManager's defaultManager()
	set directoryEnumerator to (manager's enumeratorAtURL:theURL includingPropertiesForKeys:{} options:((current application's NSDirectoryEnumerationSkipsHiddenFiles as integer) + (current application's NSDirectoryEnumerationSkipsPackageDescendants as integer)) errorHandler:(missing value))'s allObjects()
	set predicate to current application's NSPredicate's predicateWithFormat:"lastPathComponent ==[c] %@" argumentArray:theName
	set anArray to (directoryEnumerator's |path|'s filteredArrayUsingPredicate:predicate) as list
end searchWithFilename:inPath:

We could also use pathsMatchingExtensions: method and make something like this.

on filesMatchingExtensions:theExtList inPath:thePath
	set theURL to current application's |NSURL|'s fileURLWithPath:thePath
	set manager to current application's NSFileManager's defaultManager()
	set enumOptions to (current application's NSDirectoryEnumerationSkipsPackageDescendants as integer) + (current application's NSDirectoryEnumerationSkipsHiddenFiles as integer)
	set directoryEnumeratorAllObjects to (manager's enumeratorAtURL:theURL includingPropertiesForKeys:{} options:enumOptions errorHandler:(missing value))'s allObjects()
	set anArray to (directoryEnumeratorAllObjects's |path|'s pathsMatchingExtensions:theExtList)
	set theSortList to (anArray's sortedArrayUsingSelector:"compare:") as list
end filesMatchingExtensions:inPath:

@ccstone my approach in this examples was to not use a repeat loop and that makes the scripts so much faster.

The searchfs command use file system catalog search approach to search the volume of a disk and not specific part of a disk. If you limit your search the search will be faster with other approaches. And if you use spotlight you maybe are not getting what you search for but searchfs do.