英国相似的怪物

那么让我们重新审视AppleScript的错误wrote about two weeks ago, where System Events returns the wrong folder in response to a路径命令要求中的特殊文件夹用户域

简而言之,这就是错误:

set p1 to path to application support folder from user domain
-- alias "Tycho HD:Users:gruber:Library:Application Support:"

tell application "Finder"
    set p2 to path to application support folder from user domain
    -- alias "Tycho HD:Users:gruber:Library:Application Support:"
end tell

tell application "System Events"
    set p3 to path to application support folder from user domain
    -- alias "Tycho HD:Library:Application Support:"
end tell

即在系统事件告诉块,path to <some magic folder name> from user domain命令将从中返回文件夹local domain(根级库文件夹)而不是。

Thanks to dozens of helpful emails from various readers (and I thank each of you), it’s clear why this is happening: it’s a good old-fashioned scripting dictionary terminology conflict.

的StandardAdditions OSAX定义了一个“路径” command, which takes an optional ““参数当你使用参数,您必须从以下枚举常量中进行选择:

  • 系统域
  • 本地域名
  • 网络域名
  • 用户域
  • 经典领域

因此,StandardAdditions定义“用户域” as a constant, with the four-character Apple Event code ‘fldu’.

问题是系统事件定义“用户域”, not as a constant, but rather as a property of the System Events application itselfIt uses the same four-character Apple Event code, ‘fldu’.

Bear with me here, because we need a little background on how AppleScript works to truly fathom just how pernicious this particular bug is.

Compiled AppleScripts are not stored as AppleScript source code; instead, as the name of the format implies, they are stored in a compiled format, which format consists of Apple Event codesScripting dictionaries are the go-between; they map the English-like syntax of AppleScript to the underlying Apple events:

AppleScript源代码  scripting dictionaries    Apple events

翻译有两种方式You type AppleScript syntax, then when you compile the script, it gets translated to Apple eventsWhen you open a saved script, AppleScript uses the dictionaries to translate the compiled Apple events back into English-like AppleScript source code.

我们上面的问题是当我们使用StandardAdditions时路径命令,我们也想使用StandardAdditions用户域列举However, when we put this statement in a System Events tell block, System Events’s dictionary gets the first crack at the terms, and because it has its own definition of用户域, that’s the one AppleScript uses因为它使用相同的AppleScript语法the same underlying Apple Event code, there is no indication to you, the programmer, that AppleScript has resolved用户域to something other than StandardAdditions’用户域

So it compiles successfully, and when it decompiles back into AppleScript source code, the syntax looks correctBut when it executes, it returns the wrong result.

解决方法

丹尼尔Jalkutwas the first of several readers to suggest the following workaround:

A stupid, but functional workaround would be to define your own “user domain” key outside the scope of the System Events tell. For instance, if you were going to be doing a bunch of this stuff in a script, you could do something like this:

set myUserDomain to user domain

tell application "System Events"
   path to application support from myUserDomain
end tell

这里的想法是myUserDomaingives you a reference to StandardAdditions’用户域enumeration from within the context of a System Events tell block.

Another workaround suggested by several readers is to use System Events’s own means of getting references to special folders, rather than StandardAdditions’路径命令For example, to get a reference to the Fonts folder in the user domain:

tell application "System Events"
    set ff to fonts folder of user domain
end tell

But that doesn’t return a path or an alias; instead it returns a System Events宾语系统事件'class contains properties for the path (HFS-style) and POSIX path, so if what you want is the path (rather than a folder object), you can say something like this:

tell application "System Events"
    set ff to fonts folder of user domain
    set posix_path to POSIX path of ff
    set hfs_path to path of ff
end tell

posix_pathwill be something like “/Users/gruber/Library/Fonts”, and hfs_path will be something like “Tycho HD:Users:gruber:Library:Fonts:”.

因此,要获得HFS样式的路径,您可以将其汇总到一行:

tell application "System Events"
    set ff1 to path of fonts folder of user domain
end tell

与StandardAdditions语法进行比较和对比:

set ff2 to path to fonts folder from user domain

特别关注“path of“vs“路径“和”of user domain” versus “来自用户域”。

对AppleScript类似英语的语法失败的实验进行插值

这是最糟糕的AppleScriptIt was a grand and noble idea to create an English-like programming language, one that would seem approachable and unintimidating to the common userBut in this regard, AppleScript has proven to be a miserable and utter failure.

在英语中,这两个陈述应该被视为同义词:

path of fonts folder of user domain
path to fonts folder from user domain

But in AppleScript, they are not, and rather are brittlely dependent on the current contextIn the global scope, the StandardAdditions OSAX wants “path“和”user domain”; in a System Events tell block, System Events wants “path“和”用户域“。

The idea was, and I suppose still is, that AppleScript’s English-like facade frees you from worrying about computer-science-y jargon like classes and objects and properties and commands, and allows you to just say what you mean and have it just work.

But saying what you mean, in English, almost never “just works” and compiles successfully as AppleScript, and so to be productive you still have to understand all of the ways that AppleScript actually worksBut this is difficult, because the language syntax is optimized for English-likeness, rather than being optimized for making it clear just what the fuck is actually going on.

This is why Python and JavaScript, two other scripting language of roughly the same vintage as AppleScript, are not only better languages than AppleScript, but are更轻松than AppleScript, even though neither is very English-like at allPython and JavaScript’s syntaxes are much more abstract than AppleScript’s, but they are also more obvious(特别是Python,庆祝显而易见。)

In this regard, AppleScript syntax styling in your chosen script editor can provide essential cluesHere’s what the above examples look like on my machine in脚本调试器

AppleScript语法样式示例。

The gray words are language keywords, meaning they are part of the AppleScript programming language; the blue words are application keywords, meaning they are defined by a scripting dictionary.1

Note that in the first statement, inside the System Events tell block, the ““在”path of“是一种语言关键字But in the second statement, the ““在”路径“是一个应用关键字Even more confusing is that “路径“之前”“, 哪一个 ”语言关键字所以你有“走向“,其中一个是一个语言关键字,另一个是应用程序关键字。

How is this possible? Because the second “” is not a standalone keyword; it’s part of StandardAdditions’ “路径” command, which is a single token that consists of multiple space-separated wordsIn most languages, this command would have been called something like “pathTo“ 要么 ”path_to”; but in AppleScript, intra-token spaces are considered a good thing, on the grounds that they greatly increase the resemblence to EnglishIn practice, however, I believe intra-token spaces are one of the most common underlying causes of AppleScript confusion.

同样,请注意使用StandardAdditions路径syntax, the ““在”来自用户域” is an application keyword; it’s the name of a parameter defined by the路径命令Whereas in the System Events syntax, it’s just another chained “” to access a property of an object.

These prepositional differences are even more exasperating when you consider that ““和”“在AppleScript中可以互换If you can say either of these to mean the same thing within a System Events tell block:

path of fonts folder of user domain
path in fonts folder in user domain

你可以使用StandardAdditions这样说:

用户域的字体文件夹路径

那么似乎相当自然地假设”“和”” might be interchangeable with other prepositions as wellBut you can’t, and if you’re not aware that StandardAdditions’s “路径” is a single token of two words, it seems rather arbitrary, if not downright random, which prepositions are allowed where.

(AppleScript允许你抛出多余的方式一个’s — “获取fonts文件夹的路径“意味着同样的事情”get path of fonts folder“,实际上意味着同样的事情”get the the the path of the the the fonts folder” — makes it seems as though AppleScript just doesn’t care about “little words”, which is true in a handful of cases, but completely untrue in others.)

在“普通”编程语言中,相当于“path to fonts folder from user domain“可能是这样的:

path_to(“fonts folder”,“user domain”)

相当于“用户域的字体文件夹的路径“ 可能:

user_domain.fonts_folder.path

The point being that in most languages, these two calls don’t look at all similar这是件好事,因为他们at all similar: one is a global command taking two parameters, the other is a property of a property of an objectAppleScript’s slavish devotion to English-likeness, on the other hand, gives us two very different syntax constructs that read, to humans, as though they’re semantically similar.

One way to disambiguate the two syntaxes in these examples would be to use AppleScript’s(apostrophe-s) “possessive” operator instead of the ““关键字The two statements in the following example are more or less equivalent:

tell application "System Events"
    set ff1 to path of fonts folder of user domain
    set ff2 to user domain's fonts folder's path
end tell

马特·纽堡,他的精彩AppleScript: The Definitive Guide, devotes an entire section to what he calls “The ‘English-likeness’ Monster” (from which section I’ve taken the title of this essay)He establishes numerous problems caused by AppleScript’s attempts to resemble English, and concludes by pointing out that even simple variable assignment in AppleScript suffers:

然后有一个事实是英语很冗长In most computer languages, you would make a variableXtake on the value 4 by saying something like this:

x = 4

在AppleScript中,您必须说出以下其中一项:

copy 4 to x
set x to 4

Doubtless not everyone would agree, but I find such expressions tedious to write and hard to readIn my experience, the human mind and eye are very good at parsing simple symbol-based equations and quasi-mathematical expressions, and I can’t help feeling that AppleScript would be much faster to write and easier to read at a glance if it expressed itself in even a slightly more abstract notational style.

回到解决方法

所以,回到这个:

tell application "System Events"
    set ff1 to path of fonts folder of user domain
end tell

You might be tempted to say, “Well, there you go — when you’re inside a System Events tell block, don’t use the StandardAdditions路径command, use System Events’ own means of accessing special folders instead.”

The problem here is that System Events doesn’t know about as many special folders as does StandardAdditionsOr, more specifically, the System Events dictionary doesn’t define names for as many special folders as does the StandardAdditions dictionary.

And, unfortunately, one of the folders System Events doesn’t know about is the one we’re interested in, the Application Support folder. Changing the above reference to the Fonts folder to:

tell application "System Events"
    set appsup to path of application support folder of user domain
end tell

甚至不会编译As of Mac OS X 10.4.2, here is the full list of special folder properties known to System Events用户域宾语:

  • 应用文件夹
  • 桌面图片文件夹
  • Folder Action脚本文件夹
  • 字体文件夹
  • 偏好文件夹
  • 脚本添加文件夹
  • 脚本文件夹
  • 可说的物品文件夹
  • 实用文件夹
  • 桌面文件夹
  • 文件夹
  • 收藏文件夹
  • 主文件夹
  • 电影文件夹
  • 音乐文件夹
  • 图片文件夹
  • 公用文件夹
  • 站点文件夹
  • 临时物品文件夹2

StandardAdditions'字典定义了几十个,3but even it doesn’t include every special Mac OS folder.

完整列表,以四字符代码的形式,is available in Apple’s Carbon developer documentationThe folder names defined in System Events’ scripting dictionary are just wrappers around these four-character codes, so, to address one of the folders that the dictionary doesn’t define, you can simply use the raw code:

tell application "System Events"
    set appsup to path of folder id "asup" of user domain
end tell

或者,使用运营商:

tell application "System Events"
    set appsup to user domain's folder id "asup"'s path
end tell

(你可以看到为什么运营商不受欢迎。)

So, at last, we’ve arrived at a complete workaround, but it requires (a) foreknowledge of the underlying用户域terminology conflict; and (b) looking up the Application Support folder’s four-character code in the Carbon developer documention.

结论和建议

My advice is not to use scripting addition commands from within application tell blocks if you can avoid itUse tell blocks only to group statements directly related to the app that is the target of the tell blockYou’re better off with multiple tell blocks for the same app interspersed with calls to scripting addition commands than a single tell block that contains calls to scripting additions.

The blame for my complaint here probably lies mostly with System Events, because its dictionary should not define a用户域与StandardAdditions冲突的对象用户域enumeration in such a way that it doesn’t produce a compile- or run-time errorOr, perhaps the blame lies mostly with the AppleScript compiler for allowing this terminology conflict to procede without error.

But I’ll also point an accusatory finger in the direction of scripting additions in generalThe problem with scripting additions is that they pollute the global namespace with their dictionary syntaxAn application can define whatever crazy terms it wants and it won’t adversely affect the rest of your script, because an app’s terms are only available within tell blocks targeting that app(或者在...内使用术语从应用程序块)。

AppleScript is actually a tiny language, with extraordinarily few keywords, but the standard scripting additions turn it into a bigger language, ripe for terminology conflicts.


  1. “应用程序关键字”还包括由脚本添加定义的术语,但重点是,这些是由字典定义的术语,而不是AppleScript语言本身。↩︎

  2. 趣味事实:你可以要求“临时物品文件夹” from the user domain using either StandardAdditions or System Events, and you’ll get the path to “~/Library/Caches/TemporaryItems” instead of a sub-folder within “/private/tmp” or “private/var/tmp” (which is where临时物品文件夹如果你不坚持就解决了用户域部分)。↩︎

  3. 见第Neuburg的341AppleScript:权威指南为一个完整的清单。↩︎