Is there a better method than using Satimage.osax?
### REQUIRES Satimage.osax ###
# It handles Folders and Files, and Files without an extension
tell application "Finder" to set fItemPath to POSIX path of (item 1 of (get selection as alias list))
set fItemBaseName to change ".+\\/([^\\/\\.]+).*" into "\\1" in fItemPath syntax "PERL" with regexp
-->TEST_DEV for a folder
-->TEST Chrome Archive for a file
But maybe there is a better method without using RegEx?
BTW, I have learn that one should always use the clause syntax âPERLâ with the Satimage.osax find and change commands. By default it uses RUBY, which is not good.
tell application "Finder" to set fItemPath to POSIX path of (item 1 of (get selection as alias list))
-- Delete anything in the path which is a slash optionally followed by sections ending with a slash (except at the very end),
-- or which is a dot followed by characters which don't include dots or slashes (except possibly for a slash at the very end),
-- or which is otherwise a slash at the very end.
set fItemBaseName to change "/(?:[^/]*/)*(?!$)|\\.[^\\./]+/?$|/$" into "" in fItemPath syntax "PERL" with regexp
Vanilla, ASObjC, and shell scripts all offer other methods. I imagine that whether or not any of them are better than using Satimage would depend on where youâre starting, what else youâre doing, what you find easiest, and for whom youâre writing the script.
Best is pretty subjective. Iâd argue that this is more self-documenting to all but regex fiends:
use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use scripting additions
tell application "Finder" to set fItemPath to POSIX path of (item 1 of (get selection as alias list))
set fItemBaseName to (current application's NSString's stringWithString:fItemPath)'s lastPathComponent()'s stringByDeletingPathExtension() as text
it takes a lot of practice to become fluent in the operations of a Kleene algebra (hence a lot of rationalisable but still obfuscating over-use of regular expressions), and
only when they have come to feel entirely natural and familiar, do you finally discover that in fact you rarely need them
PS, being, alas, much lazier than Shane, I would probably do it by composing something from a set of pasted generic functions:
on run
map(takeBaseName, selectedPaths())
end run
-- selectedPaths :: () -> [FilePath]
on selectedPaths()
tell application "Finder"
if (count of windows) > 0 then
script
on |λ|(x)
set fp to POSIX path of x
if last character of fp = "/" then
text 1 thru -2 of fp
else
fp
end if
end |λ|
end script
my map(result, selection as alias list)
else
{}
end if
end tell
end selectedPaths
-- GENERICS -----------------------------------------------------------
-- intercalateString :: String -> [String] -> String
on intercalateString(sep, xs)
set {dlm, my text item delimiters} to {my text item delimiters, sep}
set s to xs as text
set my text item delimiters to dlm
return s
end intercalateString
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
-- splitOn :: String -> String -> [String]
on splitOn(strDelim, strMain)
set {dlm, my text item delimiters} to {my text item delimiters, strDelim}
set xs to text items of strMain
set my text item delimiters to dlm
return xs
end splitOn
-- takeBaseName :: FilePath -> String
on takeBaseName(strPath)
if strPath â "" then
if text -1 of strPath = "/" then
""
else
set fn to item -1 of splitOn("/", strPath)
if fn contains "." then
intercalateString(".", items 1 thru -2 of splitOn(".", fn))
else
fn
end if
end if
else
""
end if
end takeBaseName
Iâd do it using either my script or Shaneâs. Both only remove the last elements of file names containing more than one dot and both produce something resembling a name when the selected itemâs a folder or a package.
(Interpreting the latter as a reference to no file in the parent folder)
For various reasons, I like to use a function which returns a value from the input string only, rather than from a combination of that and the current state of the filesystem, which I prefer to probe separately.
(When in doubt, for better or for worse, I adopt the conventions of eponymous functions in the standard Haskell modules - it just simplifies the mental model for me when I am working with various different scripting languages).
PS in the light of @NigelGarveyâs point about selecting items which yield a final / in their url (folder and packages), Iâve adjusted selectedPaths() above to shed any final â/â, allowing for more useful composition with takeBaseName()
But at this point, your scriptâs still stripping everything after and including the first dot in any name instead of the last. eg. âScreen Shot 2018-03-21 at 13.44.58.pngâ is being returned as âScreen Shot 2018-03-21 at 13â instead of âScreen Shot 2018-03-21 at 13.44.58â.
tell application "Finder" to set HFSPath to (beginning of (get selection)) as text
takeBaseName(HFSPath, ":")
on takeBaseName(pathOrName, pathDelimiter)
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to pathDelimiter
if (pathOrName ends with pathDelimiter) then
set baseName to text item -2 of pathOrName
else -- pathOrName either doesn't end with or doesn't contain a path delimiter.
set baseName to text item -1 of pathOrName
end if
if (baseName contains ".") then
set AppleScript's text item delimiters to "."
set baseName to text 1 thru text item -2 of baseName
end if
set AppleScript's text item delimiters to astid
return baseName
end takeBaseName
As a footnote on parameterising the path delimiter, and the choice of argument order (which prima facie seems arbitrary), if you flip the arguments, bringing the (less frequently varying) path delimiter argument into first position, you can then derive, at runtime, a path-delimiter-specific variant by applying a generic curry function.
This can turn out to be useful if you want to map your takeBaseName over a list of file paths.
For example:
on run
-- ALLOWING FOR MULTIPLE SELECTIONS IN THE FINDER
tell application "Finder" to set HFSPaths to selection as alias list
map(|λ|(":") of curry(takeBaseName), HFSPaths)
end run
on takeBaseName(pathDelimiter, fp)
set pathOrName to fp as text
set astid to AppleScript's text item delimiters
set AppleScript's text item delimiters to pathDelimiter
if (pathOrName ends with pathDelimiter) then
set baseName to text item -2 of pathOrName
else -- pathOrName either doesn't end with or doesn't contain a path delimiter.
set baseName to text item -1 of pathOrName
end if
if (baseName contains ".") then
set AppleScript's text item delimiters to "."
set baseName to text 1 thru text item -2 of baseName
end if
set AppleScript's text item delimiters to astid
return baseName
end takeBaseName
-- GENERICS ------------------------------------------------------------------
-- curry :: ((a, b) -> c) -> a -> b -> c
on curry(f)
script
on |λ|(a)
script
on |λ|(b)
|λ|(a, b) of mReturn(f)
end |λ|
end script
end |λ|
end script
end curry
-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
tell mReturn(f)
set lng to length of xs
set lst to {}
repeat with i from 1 to lng
set end of lst to |λ|(item i of xs, i, xs)
end repeat
return lst
end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper
-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
if class of f is script then
f
else
script
property |λ| : f
end script
end if
end mReturn
Wow! Thanks guys for a very spirited and informative discussion!
I have selected the solution by @ShaneStanley as the best.
But the solution by @NigelGarvey is a close second.
Sorry Shane, but I have to disagree with you that âBest is pretty subjectiveâ. If the proper testing and evaluation is done, like here in this thread, then, IMO, âbestâ becomes objective.
Nigel made it clear:
Best Solution
because it is very accurate (100% AFAIK), does not require any external osax/libs, and it is also compact and easy to read, even if you know nothing about ASObjC: lastPathComponent()'s stringByDeletingPathExtension()
Second Best
It is a great solution, but RegEx requires either an external OSAX or more complicated ASObjC code, and is harder to read, if you didnât write it.
Nigel, thanks for finding and correcting the error in my RegEx. Yours is definitely better.
Well, if by âfiendsâ you mean avid user and advocate, then I would plead guilty.
RegEx is but one of many tools in my tool kit. I always try to use the best tool (that I know how to use) for the job. I do have to say that I find RegEx very useful, often using it many times a day. Probably my most frequent use is in BBEdit, where the Find/Replace tool is excellent, and I use it to clean up a variety of data.
Just one more point for those interested in RegEx. I find the tool at RegEx101.com to be excellent for developing, testing, and documenting RegEx patterns.
Many thanks again to all who participated in this thread.
To be picky, the original question referred to the âBase Finder Item Nameâ. That implies how the name looks in the Finder, which is not necessarily what any of these scripts return.
Even ignoring potential localization issues, only Nigelâs delimiters-based code will give the correct result for names containing â/â.
The method is presumably called lastPathComponent rather than pathName because of the distinction.
Thanks for being âpickyâ, LOL.
But you are inferring more than I was implying.
I used the phrase âFinder Itemâ because I donât know of another way to indicate either a file or folder. So I call them âFinder Itemsâ. What would you suggest?
Using â/â or â:â in a file name in any OS is asking for disaster, IMO. I never do it, even though technically the Mac Finder permits â/â.
OK, so what do you want us to do with that great piece of info? Are you saying that we should not use your script? Or is this just a case of too much information? LOL
Now, on to more important matters. I came back to the forum tonight (this morning) to ask you a question about using your method in another script.
I have this script, which I think you wrote, that returns a list of paths. I want it to also return the base name of the file/folder:
set theURLs to fileManager's contentsOfDirectoryAtURL:sourceURL includingPropertiesForKeys:{} options:(current application's NSDirectoryEnumerationSkipsHiddenFiles) |error|:(missing value)
set theURLs to theURLs's allObjects()
set foundItemList to current application's NSPredicate's predicateWithFormat_("lastPathComponent matches %@", findPattern)
set foundItemList to theURLs's filteredArrayUsingPredicate:foundItemList
set foundPathList to (foundItemList's valueForKey:"path") as list
set foundNameList to {}
repeat with oPath in foundPathList
set end of foundNameList to (current application's NSString's stringWithString:oPath)'s lastPathComponent()'s stringByDeletingPathExtension() as text
end repeat
This seems like brute force to get the base name. Is there a better way?
I tried: set foundNameList to (foundItemList's valueForKey:"name") as list
but that failed.
No. I was just musing out loud that name means different things in different contexts, and that what you intend to do with it can affect how you should discover it.
Sure. URLs also have a lastPathComponent property, so:
set foundNameList to (foundItemList's valueForKeyPath:"lastPathComponent.stringByDeletingPathExtension") as list