Get all subfolders in a target folder

As far as I am aware, it is not possible with a shortcut to recursively get all subfolders (including empty subfolders) in a target folder. A possible alternative is to call a second shortcut that is recursive, but I can’t seem to get this to work. Thanks for any help.

This is the calling shortcut.

Call Get Folders.shortcut (21.9 KB)

This is the called shortcut. I’m not sure if the recursion stops prematurely, or if the TheSubfolders list is reset with each recursion.

Get Folders.shortcut (22.2 KB)

In this case the help of the shell for example

set rootFolder to quoted form of POSIX path of (choose folder)
do shell script "/usr/bin/find " & rootFolder & " -type d"

might be more efficient.

1 Like

Thanks Stefan. That approach works well and will be useful in many instances (see screenshot below). However, the Find command returns POSIX paths, which cannot be used in many shortcut actions. I don’t believe there is a shortcut equivalent of AppleScript’s POSIX file.

Please try this AppleScript, it converts the POSIX paths to the universal class furl

set rootFolder to quoted form of POSIX path of (choose folder)
set allFolders to paragraphs of (do shell script "/usr/bin/find " & rootFolder & " -type d")
set theResult to {}
repeat with aFolder in allFolders
	set end of theResult to aFolder as «class furl»
end repeat
return theResult

1 Like

Thanks Stefan. That does work, and, FWIW, Finder can also be used. In both cases, folder objects that can be used directly in a shortcut action are returned.

I know, but it’s horribly slow.

Happened to be browsing here and this script caught my attention. Any chance to add to this script the ability to print a text file to the desktop?

Sure, this script creates a plain text file on desktop with name FoldersOf plus the name of the chosen folder

set rootFolder to (choose folder)
tell application "System Events" to set folderName to name of rootFolder
set destinationFile to quoted form of (POSIX path of (path to desktop) & "FoldersOf" & folderName & ".txt")
do shell script "/usr/bin/find " & quoted form of POSIX path of rootFolder & " -type d > " & destinationFile

Thank you, works perfectly, and the text file will prove very useful.

I decided to see if Google AI might have a solution. After creating the two shortcuts, it occurred to me that they were surprisingly similar to the shortcuts I included in post 1. The Google AI shortcuts didn’t work (even after correcting a few obvious errors).

I think the answer may lie in returning as input a list of the subfolders when the second shortcut calls itself. I’ll keep at it.

BTW, if including a screenshot of the Google AI suggestion violates something, please let me know.

I’m making progress. I found that the recursion was working and that the subfolders were found. The issue is the procedure by which the subfolders are made into a list and returned to the calling shortcut.

To confirm this, I rewrote the recursive shortcut to append the POSIX paths of the subfolders to a text file, which worked well. To test these shortcuts, the target folder and the folder that will contain the text file need to be set, and the name of the called shortcut may also need to be set.

If this was an AppleScript, I would make the recursive shortcut into a handler, and add the subfolders to a list, which I would make global. It’s the global part that can’t be done with a shortcut.

Anyways, at least I understand the issue.

Save Folders One.shortcut (22.1 KB)

Save Folders Two.shortcut (22.6 KB)

This is a revision of my post on 2025-08-24 that stated folder recursion, but used the wrong FileManager function to achieve that goal. It now does folder recursion returning an array of URLs and optionally (by uncommenting), a list of HFS paths for those URLs.

The entire Swift code that goes into the Run Shell Script:

import Foundation
import OSAKit

/* subDirectories URL extension
   Leo Dabus at:
   https://stackoverflow.com/questions/34388582/get-subdirectories-using-swift
*/

extension URL {
    func subDirectories() -> [URL] {
        // @available(macOS 10.11, iOS 9.0, *)
        guard hasDirectoryPath else { return [] }
        let foo = FileManager.default.enumerator(at: self,
                                                 includingPropertiesForKeys: nil,
                                                 options: [.skipsPackageDescendants, .skipsHiddenFiles],
                                                 errorHandler: nil)
        return (foo!.allObjects).compactMap { $0 as? URL }.filter { $0.hasDirectoryPath }
    }
}

var args: [URL]
var subDirs: [URL]

args = CommandLine.arguments.dropFirst().map { URL(fileURLWithPath: $0).standardizedFileURL }

// args.first contains the folder URL selected in the Finder
subDirs = args.first!.subDirectories()

let tildePath = (args.first!.path as NSString).abbreviatingWithTildeInPath
print("Subfolders of:  \(tildePath)")
print(String(repeating: "_", count: 14), terminator: "\n")

// as folder names
subDirs.forEach {
   print(($0.path as NSString).abbreviatingWithTildeInPath)
}

// as optional HFS subfolder paths
/*
print("\n")
print("HFS subfolders of:  \(tildePath)")
print(String(repeating: "_", count: 18), terminator: "\n")
subDirs.forEach { print(($0.path as NSString).value(forKey: "_osa_hfsPath")!) }
*/

The Test folder hierarchy:

Screenshot 2025-08-24 at 11.47.01 AM

and the result of the Shortcut (with HFS output suppressed):

Screenshot 2025-08-24 at 12.16.18 PM

and what the HFS output would appear as if enabled:

Screenshot 2025-08-24 at 12.19.00 PM

The Shortcut itself:

Clearly, one is not going to open this in Script Editor… :nerd_face:

I finally gave up on the goal set in post 1 and thought I’d finish up with a few comments and working shortcut examples. I incorporated Stefan’s suggestion in post 4 in a shortcut, and I tested it against my Finder shortcut. My observations are:

  • Both shortcuts returned shortcut folders that could be used in other shortcut actions.

  • With a folder that contained 26 subfolders, Stefan’s suggestion in a shortcut was faster at 290 milliseconds as compared with 350 milliseconds for my Finder shortcut.

  • Tested on a folder that contained packages, Stefan’s suggestion in a shortcut returned folders in the packages, and my Finder shortcut did not.

  • Stefan’s suggestion in a shortcut returned hidden folders. My Finder shortcut did not return hidden folders unless the Finder was set to show hidden folders.

The following shortcuts input a shortcut folder and output a list of shortcut folders.

Get Folders Shell.shortcut (22.3 KB)

Get Folders Finder.shortcut (22.1 KB)

BTW, I was able to get ASObjC code to run in a shortcut, but the result was slow.

Here is a Quick Action Shortcut that gets only the sorted subfolder hierarchy of a Finder selected folder. It uses a Run AppleScript and ASOC to quickly produce three display dialogs of the folder hierarchy:

  • POSIX paths

  • URL paths

  • HFS paths

Tested on macOS 15.6.1 with Script Debugger 8.0.10 and Script Editor 2.11.

Run AppleScript content:

use framework "Foundation"
use framework "OSAKit"
use AppleScript version "2.4"
use scripting additions

property ca : current application

on run {input, parameters}
	
	set mutPath to ca's NSMutableArray's new()
	set mutURL to ca's NSMutableArray's new()
	set mutHFS to ca's NSMutableArray's new()
	
	set ascend to ca's NSSortDescriptor's sortDescriptorWithKey:"" ascending:true
	set theFolder to ca's NSURL's fileURLWithPath:(POSIX path of input)
	
	mutPath's addObjectsFromArray:(ca's NSArray's arrayWithArray:(my recursiveFolderEnumerate(theFolder)))
	mutPath's sortUsingDescriptors:{ascend}
	
	my convert_array(mutPath, mutURL, "fileURL")
	my convert_array(mutPath, mutHFS, "_osa_hfsPath")
	
	display dialog (mutPath's componentsJoinedByString:return) as text
	display dialog (mutURL's componentsJoinedByString:return) as text
	display dialog (mutHFS's componentsJoinedByString:return) as text
	
	return input
end run

on recursiveFolderEnumerate(afolder)
	set fm to ca's NSFileManager's defaultManager()
	-- presumption is that folders do not have extensions
	set pred to ca's NSPredicate's predicateWithFormat:"SELF.pathExtension == '' "
	set theKeys to {ca's kCFURLIsDirectoryKey}
	set theOptions to (ca's NSDirectoryEnumerationSkipsPackageDescendants as integer) + (ca's NSDirectoryEnumerationSkipsHiddenFiles as integer)
	set enumerator to (((fm's enumeratorAtURL:afolder includingPropertiesForKeys:theKeys options:theOptions errorHandler:(reference))'s allObjects()'s ¬
		valueForKey:"path")'s filteredArrayUsingPredicate:pred) as list
	return enumerator
end recursiveFolderEnumerate

on convert_array(src_ary, dest_ary, keyStr)
	try
		(dest_ary's setArray:(src_ary's allObjects()'s valueForKeyPath:keyStr))
	on error
		-- On macOS 15.6.1, Apple's Script Editor 2.11 accepts fileURL and Script Debugger 8.0.10 throws a KVC error 
		if keyStr = "fileURL" then
			repeat with apath in src_ary
				(dest_ary's addObject:(ca's NSURL's fileURLWithPath:apath))
			end repeat
		end if
	end try
	return
end convert_array

And the Shortcut: