Batch retrieval of HFS paths and AppleScript aliases

Applescript, the shell, ASObjC/Cocoa, and third-party osaxen offer a variety of techniques for getting the POSIX paths of a folder’s items efficiently, as discussed in a recent forum post. In contrast, the options for getting HFS paths and Applescript aliases are more limited, since the shell and Cocoa deal almost exclusively in POSIX paths. A universally applicable approach is first to get the POSIX paths of the desired items using any of the above techniques, and then to convert each POSIX path to its corresponding HFS path or Applescript alias via Applescript coercion in a repeat loop. For example:


tell application "System Events" to set posixPaths to POSIX path of items of folder parent_folder_hfs_path
--or--
set posixPaths to (do shell script "find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1")'s paragraphs

set {hfsPaths, applescriptAliases} to {{}, {}}
repeat with currPath in posixPaths
	set end of hfsPaths to currPath as POSIX file as text
	set end of applescriptAliases to currPath as POSIX file as alias
end repeat

However, if for whatever reason one wishes to avoid using a repeat loop and instead get the HFS paths and/or Applescript aliases directly via batch retrieval, the options are more limited. The following discussion describes various batch retrieval techniques. At the end of the discussion, a hybrid shell-Applescript technique is described that was first presented in the recent forum post with minor modifications since the original post.

Notes:
(1) This discussion covers the non-interactive retrieval of HFS paths and Applescript aliases, not interactive retrieval, for example, with the choose file command.
(2) In the examples below, the variable parent_folder_hfs_path is assumed to contain the full HFS path of the target folder whose items are to be retrieved. In the cases where a folder’s items are retrieved selectively, the retrieval will be based on the item having a .txt or .rtf file extension for demonstration purposes.

One option is with the Finder:


-- To get the HFS paths of a folder's items globally or selectively:
set {tid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, linefeed}
try
	tell application "Finder"
		set allHfsPaths to ((items of folder parent_folder_hfs_path) as text)'s text items
		set selectiveHfsPaths to ((items of folder parent_folder_hfs_path whose (name extension is in {"txt", "rtf"})) as text)'s text items
	end tell
end try
set AppleScript's text item delimiters to tid

-- To get the Applescript aliases of a folder's items globally or selectively:
tell application "Finder"
	set allApplescriptAliases to (items of folder parent_folder_hfs_path) as alias list
	set selectiveApplescriptAliases to (items of folder parent_folder_hfs_path whose (name extension is in {"txt", "rtf"})) as alias list
end tell

Caveats with the Finder approach:
(1) Selective item retrieval via the whose clause can be prohibitively slow with a folder contain hundreds or even several dozens of items
(2) Hidden items are not retrieved

Another option is with the System Events application:


-- To get the HFS paths of a folder's items globally or selectively:
tell application "System Events"
	set allHfsPaths to path of items of folder parent_folder_hfs_path
	set selectiveHfsPaths to path of items of folder parent_folder_hfs_path whose ((name extension = "txt") or (name extension = "rtf")) -- note that the "name extension is in {...}" construct does not work with the System Events application
end tell

Caveats with the System Events approach:
(1) As with the Finder approach, selective item retrieval via the whose clause can be prohibitively slow with a folder contain hundreds or even several dozens of items
(2) Applescript aliases cannot be retrieved directly but must be acquired through coercion in a repeat loop

A third option is with an osax. Osaxen typically offer siginificant benefits over “vanilla” solutions. The following two osaxen exhibit faster execution speed, more robust selective item retrieval, and more terse coding than the previously described “vanilla” solutions:


-- To get the HFS paths of a folder's items globally or selectively using DJ Bazzie Wazzie's AppleScript Toolbox osax:
set allHfsPaths to AST list folder parent_folder_hfs_path with returning HFS paths and showing invisibles without listing subfolders
set selectiveHfsPaths to AST list folder parent_folder_hfs_path matching regex "\\.(txt|rtf)$" with returning HFS paths and showing invisibles without listing subfolders

-- To get the Applescript aliases of a folder's items globally or selectively using Satimage's osax:
set allApplescriptAliases to list files (parent_folder_hfs_path as alias) as alias with invisibles without recursively
set selectiveApplescriptAliases to list files (parent_folder_hfs_path as alias) of extension {"txt", "rtf"} as alias with invisibles without recursively

Caveats of an osax approach are those of any third-party solution:
(1) The user must install the osax, and portability of a given osax solution is limited by the extent to which other users are willing to install the osax
(3) The input and output options are confined to those implemented by the developer (e.g., no Applescript alias output with AppleScript Toolbox, and no HFS path output with Satimage’s osax)
(4) An osax may break if the developer ceases its development
Still, the above two osaxen are robust and efficient tools for the batch retrieval of HFS paths and Applescript aliases.

That completes the discussion of conventional means of accomplishing repeat-loop-less batch retrieval of HFS paths and Applescript aliases of a folder’s items.

Note the absence of any shell or ASObjC/Cocoa solutions. That absence reflects the foreignness of the concept of HFS paths to the shell and Cocoa. Not that it is impossible to convert a POSIX path to an HFS path with a purely shell or Cocoa solution, but one must jump through significant hoops to do so. Here are a couple of proof-of-concept solutions that are for demonstration purposes only and not to be used in the real world, along with the proper way to perform the conversions via Applescript coercion:


-- To convert an individual POSIX path to an HFS path the hard way with the shell:
set hfsPath to do shell script "path=$(printf '%q' " & posixPath's quoted form & " | sed -E 's/^[/]Volumes[/]//; s/[/]/:/g; s/[\\\\]([^\\\\])/\\1/g'); [[ ${path:0:1} == : ]] && dsk=$(diskutil info / | sed -En '/^[[:blank:]]*Volume Name:[[:blank:]]*(.*)$/{s//\\1/p;q;}'); echo \"$dsk$path\""

-- To convert an individual POSIX path to an HFS path the hard way with ASObjC/Cococa:
set pathComponents to ((current application's NSFileManager)'s defaultManager()'s componentsToDisplayForPath:posixPath)'s mutableCopy()
pathComponents's replaceObjectAtIndex:((pathComponents's |count|()) - 1) withObject:(((current application's NSString)'s stringWithString:posixPath)'s lastPathComponent)
set hfsPath to (pathComponents's componentsJoinedByString:":") as text

-- To convert an individual POSIX path to an HFS path the easy (and right) way with Applescript:
set hfsPath to posixPath as POSIX file as text
--or--
set hfsPath to POSIX file posixPath as text

-- To convert an individual POSIX path to an Applescript alias the easy (and right) way with Applescript:
set applescriptAlias to posixPath as POSIX file as alias
--or--
set applescriptAlias to POSIX file posixPath as alias

As alluded to earlier, the shell is particularly good at performing a batch retrieval of POSIX paths through its find command. For example:


-- To get the POSIX paths of a folder's items globally:
set posixPaths to (do shell script "find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1")'s paragraphs

-- To get the POSIX paths of a folder's items selectively, in this case all items with a .txt or .rtf file extension:
set posixPaths to (do shell script "find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1 \\( -iname '*.txt' -o -iname '*.rtf' \\)")'s paragraphs

The shell’s powerful find command and text-editing capabilities plus Applescript’s efficient coercion tools suggest the possibility of combining the two into a hybrid solution. Such a hybrid solution was presented in the recent forum post. It has the following features:
(1) Gets the POSIX paths of a folder’s items globally or selectively via the shell’s find command
(2) Uses shell sed substitution to convert the POSIX paths into the text representation of a list of Applescript conversion expressions
- The first two sed substitutions escape literal backslash or double-quote characters that may be present in any paths
- The second two sed substitutions transform the POSIX paths into the text representation of a list of Applescript conversion expressions
- The special character & is leveraged to reign in what would otherwise be a daunting number of backslash characters in the sed substitution expressions
(3) Executes the text representation via an Applescript run script command to return a list of HFS paths and/or Applescript aliases

A special thanks goes to Nigel Garvey for helpful optimizations of the sed command.


-- To get the HFS paths of a folder's items globally:
set allHfsPaths to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1 | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as text,¬/; $s/,¬$//')}\"")

-- To get the HFS paths of a folder's items selectively:
set selectiveHfsPaths to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1 \\( -iname '*.txt' -o -iname '*.rtf' \\) | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as text,¬/; $s/,¬$//')}\"")

-- To get the Applescript aliases of a folder's items globally:
set allApplescriptAliases to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1 | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as alias,¬/; $s/,¬$//')}\"")

-- To get the Applescript aliases of a folder's items selectively:
set selectiveApplescriptAliases to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " -mindepth 1 -maxdepth 1 \\( -iname '*.txt' -o -iname '*.rtf' \\) | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as alias,¬/; $s/,¬$//')}\"")

The user modifies two parts of the expression:
(1) parent_folder_hfs_path
- The variable’s value should be set to the full HFS path of the folder containing the items to be retrieved
(2) The part of the expression represented by XXX, which must be preceded and followed by one space character:
…" & parent_folder_hfs_path’s POSIX path’s quoted form & " XXX | sed -E '…, comprising the find command’s primaries controlling the folder items that are to be returned. In the above examples, the primaries take either of two forms:
For global folder item retrieval:
-mindepth 1 -maxdepth 1 → matches all folder items that are one nesting level deep in the folder
or
For selective folder item retrieval:
-mindepth 1 -maxdepth 1 \( -iname ‘.txt’ -o -iname '.rtf’ \) → matches all folder items that are one nesting level deep in the folder and that have file extension .txt or .rtf (matched in a case-insensitive manner)

No other part of the expression should be modified.

Caveats with the hybrid shell/Applescript approach:
1) The combined use of run script and do shell script entails a fixed time cost of about two to three hundredths of a second, which may or may not be significant depending on a given script’s circumstances
2) Knowledge of the shell’s find command is needed to harness its full power in retrieving a folder’s items selectively, a topic left for another discussion (or book!)

So to summarize, four general approaches are described for the batch retrieval of the HFS paths and Applescript aliases of a folder’s items globally or selectively without the use of a repeat loop, utilizing:
1) The Finder application
2) The System Events application
3) An osax
4) A recently described hybrid technique that combines the strengths of the shell and Applescript in a single run script/do shell script compound command

Yet another way would be to use System Events to get the HFS paths directly and sed to filter and edit them for run script. It would make the shell script less of a tangle! :slight_smile:


set parent_folder_hfs_path to (path to documents folder as text)
set requiredExtensions to {"rtf", "txt"}

-- To get the HFS paths of a folder's items globally or selectively:
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
tell application "System Events" to set allHfsPaths to (path of items of folder parent_folder_hfs_path) as text
if (requiredExtensions is {}) then
	set lineFilter to "" -- No filtering.
else
	set AppleScript's text item delimiters to "|\\."
	set lineFilter to "/(\\." & requiredExtensions & ")$/"
end if
set AppleScript's text item delimiters to astid

set editedResult to (do shell script "sed -En '" & lineFilter & "{s/\\\\|\\\"/\\\\&/g; s/^.+$/\"&\" as alias,¬/p;}' <<<" & quoted form of allHfsPaths)
if (count editedResult) > 0 then
	set filteredAliases to run script ("{" & text 1 thru -3 of editedResult & "}")
else
	set filteredAliases to {}
end if

Edit: Sorry. I didn’t allow for zero finds or test with my booby-trapped filename. The above script’s now corrected.

And before Shane wakes up: :wink:

use AppleScript version "2.5" -- El Capitan (10.11) or later.
use framework "Foundation"
use scripting additions

set parent_folder_hfs_path to (path to documents folder as text)
set requiredExtensions to {"rtf", "txt"}

set parentFolderURL to (current application's class "NSArray"'s arrayWithArray:(parent_folder_hfs_path as alias as list))'s firstObject()
set allItems to current application's class "NSFileManager"'s defaultManager()'s contentsOfDirectoryAtURL:(parentFolderURL) includingPropertiesForKeys:{} options:(0) |error|:(missing value)
if (requiredExtensions is not {}) then
	set URLFilter to current application's class "NSPredicate"'s predicateWithFormat:("pathExtension IN %@") argumentArray:({requiredExtensions})
	set allItems to allItems's filteredArrayUsingPredicate:(URLFilter)
end if

-- List of file:
return allItems as list

-- Or list of HFSPath:
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to linefeed
set allItems to (allItems as list as text)'s text items
set AppleScript's text item delimiters to astid
return allItems

Your examples do help to reduce the cluttered appearance.

If one wishes to use the single-line form of the run script/do shell script command, the following pseudo-code should help to clarify what the user does modify (anything within square brackets […]) and does not modify (everything else):


set parent_folder_hfs_path to "[...put parent folder's full HFS path here...]"

-- To get the HFS paths of a folder's items:
set hfsPaths to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " [...put the find command's primaries, i.e., filtering expressions, here...] | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as text,¬/; $s/,¬$//')}\"")

-- To get the Applescript aliases of a folder's items:
set applescriptAliases to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " [...put the find command's primaries, i.e., filtering expressions, here...] | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as alias,¬/; $s/,¬$//')}\"")

An alternative way of formulating the expressions that may also help to promote clarity is as follows (once again, modify only that which is within square brackets […]):


set parent_folder_hfs_path to "[...put parent folder's full HFS path here...]"

-- To get the HFS paths of a folder's items:
set hfsPaths to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " " & ¬
	"[...put the find command's primaries, i.e., filtering expressions, here without any leading or trailing spaces...]" & ¬
	" | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as text,¬/; $s/,¬$//')}\"")

-- To get the Applescript aliases of a folder's items:
set applescriptAliases to run script (do shell script "echo \"{$(find " & parent_folder_hfs_path's POSIX path's quoted form & " " & ¬
	"[...put the find command's primaries, i.e., filtering expressions, here without any leading or trailing spaces...]" & ¬
	" | sed -E 's/\\\\/&&&&/g; s/\\\"/\\\\&/g; s/^.*$/\\\"&\\\" as POSIX file as alias,¬/; $s/,¬$//')}\"")

Reiterating the earlier examples, one would replace […put the find command’s primaries, i.e., filtering expressions, here…] with:

 [b]-mindepth 1 -maxdepth 1[/b]

to select all items at the top nesting level of the parent folder, or

 [b]-mindepth 1 -maxdepth 1 \\( -iname '*.txt' -o -iname '*.rtf' \\)[/b]

to select all items at the top nesting level of the parent folder whose file extension is .txt or .rtf.

Keep in mind that the find command’s filtering expressions are within the do shell script command’s double-quoted string object. Therefore, any literal backslashes and double-quote characters in the filtering expressions need to be escaped with a single backslash character. This is demonstrated in the escaped literal backslashes before the left and right parentheses in -mindepth 1 -maxdepth 1 \( -iname ‘.txt’ -o -iname '.rtf’ \). (The find command requires a single backslash before grouping parenthesis characters.)