It only returned folder/files one level deep, and it returned hidden files (mainly .DS_Store).
Here is a recursive version (much neater)
Try this one
on run
local theFolder
set theFolder to (choose folder) as text
set folderTree to getFolderTree(theFolder)
end run
on getFolderTree(theFolder)
local theFolders, folderTree, theFiles
set folderTree to (theFolder as text) & linefeed
set {ATID, text item delimiters} to {text item delimiters, linefeed}
tell application "System Events"
set theFiles to (name of every file in folder theFolder whose visible is true)
sortFolders(theFiles) of me
if theFiles ≠ {} then set folderTree to folderTree & theFiles as text
try
set theFolders to (path of every folder of folder theFolder) --as list
on error
tell me to display alert "No folders were found in the selected folder"
error number -128
end try
end tell
set folderTree to folderTree & linefeed & linefeed
sortFolders(theFolders)
repeat with aFolder in theFolders
set folderTree to folderTree & getFolderTree(contents of aFolder)
end repeat
set text item delimiters to ATID
return folderTree
end getFolderTree
on sortFolders(a)
repeat with i from (count a) to 2 by -1
repeat with j from 1 to i - 1
if item j of a > item (j + 1) of a then
set {item j of a, item (j + 1) of a} to {item (j + 1) of a, item j of a}
end if
end repeat
end repeat
end sortFolders
I tested the script on my test folder and it worked fine. The timing result was 320 milliseconds.
I was trying my script above with a folder that had over 9000 files.
It took forever and I had to increase the timeout value so that System Events would return the file list.
It seems that the “whose” clause really slows down the result.
If I remove the whose clause, it responded almost instantaneously.
So I decided to parse the list using AppleScript to remove invisible files from the result.
It is much faster!
Also the sort routine was extremely slow, so I replaced it with a combSort which is way faster than the bubble sort that was used before.
on run
local theFolder
set theFolder to (choose folder) as text
set folderTree to getFolderTree(theFolder)
saveFolderTree(folderTree)
end run
on getFolderTree(theFolder)
local theFolders, folderTree, theFiles, visList
set folderTree to (theFolder as text) & linefeed
set {ATID, text item delimiters} to {text item delimiters, linefeed}
try
tell application "System Events"
set {theFiles, visList} to {name, visible} of files in folder theFolder --whose visible is true
end tell
on error
return ""
end try
repeat with i from (count of theFiles) to 1 by -1
if item i of visList is false then
set item i of theFiles to item 1 of theFiles
set item i of visList to item 1 of visList
set theFiles to rest of theFiles
set visList to rest of visList
end if
end repeat
set visList to missing value
combSort(theFiles)
if theFiles ≠ {} then set folderTree to folderTree & theFiles as text
try
tell application "System Events"
set {theFolders, visList} to {path, visible} of folders of folder theFolder
end tell
on error
tell me to display alert "No folders were found in the selected folder"
error number -128
end try
repeat with i from (count of theFolders) to 1 by -1
if item i of visList is false then
set item i of theFolders to item 1 of theFolders
set item i of visList to item 1 of visList
set theFolders to rest of theFolders
set visList to rest of visList
end if
end repeat
set folderTree to folderTree & linefeed & linefeed
combSort(theFolders)
repeat with aFolder in theFolders
set folderTree to folderTree & getFolderTree(contents of aFolder)
end repeat
set text item delimiters to ATID
return folderTree
end getFolderTree
on saveFolderTree(theText)
set theFile to (path to desktop as text) & "Folder Contents.txt"
try
set fnum to open for access file theFile with write permission
on error
display alert "Oops!"
return
end try
set eof fnum to 0
write theText to fnum starting at 0
close access fnum
end saveFolderTree
on combSort(alist as list) -- FASTEST
local i, j, cc, sf, ns, js, gap, pgap, sw -- ns means No Swap
script mL
property nlist : alist
end script
set sf to 1.5
set cc to count mL's nlist
set gap to cc div sf
repeat until gap = 0
repeat with i from 1 to gap
set js to cc - gap
repeat until js < 1 -- do each gap till nor more swaps
set ns to gap
repeat with j from i to js by gap
if (item j of mL's nlist) > (item (j + gap) of mL's nlist) then
set sw to (item j of mL's nlist)
set (item j of mL's nlist) to (item (j + gap) of mL's nlist)
set (item (j + gap) of mL's nlist) to sw
set ns to j
end if
end repeat
set js to ns - gap
end repeat
end repeat
set pgap to gap
set gap to gap div sf
if gap = 0 then -- no while using as integer
if pgap ≠ 1 then set gap to 1
end if
end repeat
end combSort
If you don’t mind using script libraries, this can be done with Shane’s fileManagerLib.
https://latenightsw.com/freeware/
use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions
use framework "Foundation"
use script "FileManagerLib" version "2.3.5"
on main()
set theFolder to POSIX path of (choose folder)
set filesFolders to objects of theFolder ¬
searching subfolders true ¬
include invisible items false ¬
include folders true ¬
include files true ¬
result type files list
set AppleScript's text item delimiters to {return}
set theText to filesFolders as text
saveFolderTree(theText)
end main
on saveFolderTree(theText)
set theFile to (current application's NSHomeDirectory()'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:"Folder Contents.txt"
(current application's NSString's stringWithString:theText)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end saveFolderTree
main()
This script is similar to my script in post 4 above, differing in that it only returns file names that contain a specified search string. The script creates a text file on the desktop with the matching files.
use framework "Foundation"
use scripting additions
set sourceFolder to POSIX path of (choose folder with prompt "Select a source folder:" default location (path to home folder))
set searchString to text returned of (display dialog "Enter a search string:" default answer "")
set theFiles to getFiles(sourceFolder, searchString)
if theFiles's |count|() = 0 then display dialog "No file names containing the search string were found." buttons {"OK"} cancel button 1 default button 1 with icon stop
set foldersAndFiles to getFoldersAndFiles(theFiles)
saveFoldersAndFiles(foldersAndFiles, sourceFolder)
on getFiles(sourceFolder, searchString)
set fileManager to current application's NSFileManager's defaultManager()
set sourceFolder to current application's |NSURL|'s fileURLWithPath:sourceFolder
set theKeys to {current application's NSURLIsDirectoryKey, current application's NSURLIsPackageKey}
set folderContents to (fileManager's enumeratorAtURL:sourceFolder includingPropertiesForKeys:theKeys options:6 errorHandler:(missing value))'s allObjects()
set thePredicate to current application's NSPredicate's predicateWithFormat_("lastPathComponent CONTAINS[c] %@", searchString)
set folderContents to folderContents's filteredArrayUsingPredicate:thePredicate
set theFolders to current application's NSMutableArray's new()
repeat with anItem in folderContents
set {theResult, aDirectory} to (anItem's getResourceValue:(reference) forKey:(current application's NSURLIsDirectoryKey) |error|:(missing value))
if aDirectory as boolean is true then
set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:(current application's NSURLIsPackageKey) |error|:(missing value))
if aPackage as boolean is false then (theFolders's addObject:anItem)
end if
end repeat
set theFiles to folderContents's mutableCopy()
theFiles's removeObjectsInArray:theFolders
set theDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"path" ascending:true selector:"localizedStandardCompare:"
return (theFiles's sortedArrayUsingDescriptors:{theDescriptor})
end getFiles
on getFoldersAndFiles(theFiles)
set foldersAndFiles to current application's NSMutableArray's new()
set oldFolder to (theFiles's objectAtIndex:0)'s URLByDeletingLastPathComponent()
(foldersAndFiles's addObject:(oldFolder as text))
repeat with aFile in theFiles
set newFolder to aFile's URLByDeletingLastPathComponent()
if not (newFolder's isEqual:oldFolder) then (foldersAndFiles's addObject:(linefeed & (newFolder as text)))
(foldersAndFiles's addObject:(aFile's lastPathComponent()))
set oldFolder to newFolder's |copy|()
end repeat
return foldersAndFiles's componentsJoinedByString:linefeed
end getFoldersAndFiles
on saveFoldersAndFiles(theText, theFolder)
set theFolder to (current application's NSString's stringWithString:theFolder)'s lastPathComponent()
set fileName to theFolder's stringByAppendingString:" Folder Contents.txt"
set theFile to (current application's NSHomeDirectory()'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:fileName
theText's writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end saveFoldersAndFiles
In order not to make users guess about your intentions, please be more clear. It is not clear in what form you want to get the hierarchy structure. I will give examples of saving the hierarchy as tabbed text.
I did some clean-up and optimization of my script in post 4. The timing results were 9 milliseconds when run on a folder containing 99 files in 10 folders and 110 milliseconds when run on my home folder.
use framework "Foundation"
use scripting additions
on main()
set theFolder to POSIX path of (choose folder)
set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
try
set theFolders to getFolders(theFolder)
set folderTree to getFolderTree(theFolders)
on error
display dialog "An error occurred while getting files" buttons {"OK"} cancel button 1 default button 1
end try
saveFolderTree(folderTree, theFolder)
end main
on getFolders(theFolder)
set fileManager to current application's NSFileManager's defaultManager()
set folderKey to current application's NSURLIsDirectoryKey
set packageKey to current application's NSURLIsPackageKey
set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects()
set theFolders to current application's NSMutableArray's new()
(theFolders's addObject:theFolder)
repeat with anItem in folderContents
set {theResult, aFolder} to (anItem's getResourceValue:(reference) forKey:folderKey |error|:(missing value))
if aFolder as boolean is true then
set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:packageKey |error|:(missing value))
if aPackage as boolean is false then (theFolders's addObject:anItem)
end if
end repeat
set theDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"path" ascending:true selector:"localizedStandardCompare:"
return (theFolders's sortedArrayUsingDescriptors:{theDescriptor})
end getFolders
on getFolderTree(theFolders)
set fileManager to current application's NSFileManager's defaultManager()
set folderTree to current application's NSMutableArray's new()
repeat with aFolder in theFolders
set aFolderPath to ((aFolder's |path|())'s stringByAppendingString:"/")
(folderTree's addObject:aFolderPath)
(folderTree's addObjectsFromArray:getFiles(aFolder, theFolders, fileManager))
(folderTree's addObject:"")
end repeat
return (folderTree's componentsJoinedByString:linefeed)
end getFolderTree
on getFiles(theFolder, theFolders, fileManager)
set theFiles to (fileManager's contentsOfDirectoryAtURL:theFolder includingPropertiesForKeys:{} options:4 |error|:(missing value))'s mutableCopy()
theFiles's removeObjectsInArray:theFolders
return ((theFiles's valueForKey:"lastPathComponent")'s sortedArrayUsingSelector:"localizedStandardCompare:")
end getFiles
on saveFolderTree(theString, theFolder)
set fileName to (theFolder's lastPathComponent)'s stringByAppendingString:" Folder Contents.txt"
set theFile to (current application's NSHomeDirectory()'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:fileName
(current application's NSString's stringWithString:theString)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end saveFolderTree
main()
I rewrote the above script utilizing a somewhat different approach. I thought it might be faster, but it was actually about 10 percent slower. It does not return empty folders and might be considered for this reason.
use framework "Foundation"
use scripting additions
on main()
set theFolder to POSIX path of (choose folder)
set theFiles to getFiles(theFolder)
if theFiles's |count|() is 0 then display dialog "No files found" buttons {"OK"} cancel button 1 default button 1
set folderTree to getFolderTree(theFiles)
saveFolderTree(folderTree, theFolder)
end main
on getFiles(theFolder)
set fileManager to current application's NSFileManager's defaultManager()
set theFolder to current application's |NSURL|'s fileURLWithPath:theFolder
set directoryKey to current application's NSURLIsDirectoryKey
set packageKey to current application's NSURLIsPackageKey
set folderContents to (fileManager's enumeratorAtURL:theFolder includingPropertiesForKeys:{} options:6 errorHandler:(missing value))'s allObjects() --option 6 skips hidden files and package contents
set theFolders to current application's NSMutableArray's new()
set booleanTrue to current application's NSNumber's numberWithBool:true
repeat with anItem in folderContents
set {theResult, aDirectory} to (anItem's getResourceValue:(reference) forKey:directoryKey |error|:(missing value))
if aDirectory is booleanTrue then
set {theResult, aPackage} to (anItem's getResourceValue:(reference) forKey:packageKey |error|:(missing value))
if not (aPackage is booleanTrue) then (theFolders's addObject:anItem)
end if
end repeat
set theFiles to folderContents's mutableCopy()
theFiles's removeObjectsInArray:theFolders
set pathDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"stringByDeletingLastPathComponent" ascending:true selector:"localizedStandardCompare:"
set nameDescriptor to current application's NSSortDescriptor's sortDescriptorWithKey:"lastPathComponent" ascending:true selector:"localizedStandardCompare:"
return (theFiles's valueForKey:"path")'s sortedArrayUsingDescriptors:{pathDescriptor, nameDescriptor}
end getFiles
on getFolderTree(theFiles)
set fileTree to current application's NSMutableArray's new()
set previousFolder to (theFiles's objectAtIndex:0)'s stringByDeletingLastPathComponent()
fileTree's addObject:(previousFolder's stringByAppendingString:"/")
repeat with aFile in theFiles
set aFolder to aFile's stringByDeletingLastPathComponent()
set aFileName to aFile's lastPathComponent()
if (aFolder's isEqualToString:previousFolder) is false then
(fileTree's addObject:"")
(fileTree's addObject:(aFolder's stringByAppendingString:"/"))
(fileTree's addObject:aFileName)
else
(fileTree's addObject:aFileName)
end if
set previousFolder to aFolder
end repeat
return fileTree
end getFolderTree
on saveFolderTree(theString, theFolder)
set theString to theString's componentsJoinedByString:linefeed
set fileName to ((current application's NSString's stringWithString:theFolder)'s lastPathComponent())'s stringByAppendingString:" Folder Contents.txt"
set theFile to ((current application's NSHomeDirectory())'s stringByAppendingPathComponent:"Desktop")'s stringByAppendingPathComponent:fileName
(current application's NSString's stringWithString:theString)'s writeToFile:theFile atomically:true encoding:(current application's NSUTF8StringEncoding) |error|:(missing value)
end saveFolderTree
main()