Applescript:使用新 URL 编辑 bookmark.plist 文件

Applescript: Edit bookmark.plist file with new URL

set some_host to "test.mydomain.com"

try
    -- Ping the host.
    do shell script "ping -c 1 -t 1 " & some_host
    -- Set ipAddr.
    set ipAddr to word 3 of result
    -- Combine the IP with the URL & port.
    set urlAddr to "http://" & ipAddr & ":80"
    display dialog "Connection Successful. " & urlAddr buttons {"OK"} default button 1
on error
    -- if we get here, the ping failed
    display dialog "Conection failed. " & some_host & " is down" buttons {"OK"} default button 1
    return
end try

-- Update the URL with the new IP.
tell application "System Events"
    tell property list file "~/Library/Safari/Bookmarks.plist"
    set value of property list item "key" to text of urlAddr
    end tell
end tell

这是我得到的错误。 我应该提到 "key" 将替换为我要更改的书签名称。

error "System Events got an error: Can’t set property list item \"key\" of property list file \"~/Library/Safari/Bookmarks.plist\" to \"http://000.000.000.000:80\"." number -10006 from property list item "key" of property list file "~/Library/Safari/Bookmarks.plist"

Bookmark.plist 示例:让我们用 "NVR Camera"

替换 "Key"
<dict>
        <key>URIDictionary</key>
        <dict>
            <key>title</key>
            <string>NVR Camera</string>
        </dict>
        <key>URLString</key>
        <string>https://12.123.456.789:88</string>
        <key>WebBookmarkType</key>
        <string>WebBookmarkTypeLeaf</string>
        <key>previewText</key>
        <string>your description</string>
        <key>previewTextIsUserDefined</key>
        <true/>
    </dict>

我正在尝试这样做,以便在 URL 发生变化时自动将其更新为新 IP。如果有人有更好的解决方案,我会洗耳恭听!提前致谢!

属性 列表数据与 XML 数据

这是我的 Safari bookmarks.plist 文件的摘录:

    <?xml version=\"1.0\" encoding=\"UTF-8\"?>
    <!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">
    <plist version=\"1.0\">
    <dict>
        <key>Children</key>
        <array>
            <dict>
                <key>Title</key>
                <string>History</string>
                <key>WebBookmarkIdentifier</key>
                <string>History</string>
                <key>WebBookmarkType</key>
                <string>WebBookmarkTypeProxy</string>
                <key>WebBookmarkUUID</key>
                <string>92E8B75A-B335-4CA3-8DE9-08A09817792D</string>
            </dict>
            <dict>
                <key>Children</key>
                <array>
                    <dict>
                        <key>ReadingListNonSync</key>
                        <dict>
                            <key>neverFetchMetadata</key>
                            <false/>

这只是前 24 行,其中带有标签 <key> 的标签出现了 8 次——大约每三行出现一次。您可以想象为什么 AppleScript 在被告知要获取名为 "key" 的标签时会有点困惑,因为有这么多标签。

话虽如此,但这并不是您的脚本引发错误的原因。我在 脚本编辑器 中 运行 这个命令:

    tell application "System Events" to ¬
        tell the property list file ¬
            "~/Library/Safari/Bookmarks.copy.plist" to ¬
            get every property list item whose name is "key"

(我正在使用我的 bookmarks.plist 文件的副本,因为 AppleScript 完全有能力完全搞砸它读取或写入的任何 .plist 文件。)

该命令的输出是:{},它告诉我们没有 属性 列表项标记为 key

虽然 plist 文件看起来像 XML,但 AppleScript 处理数据的方式与 XML 文件不同。对于 XML 文件,您可以使用其标签名称引用元素,例如 <key>。对于 属性 列表文件,元素在 key/value 对中被引用,因此,XML 数据中的 <key> 标记将具有 value 由名称 "key" 表示,对于 plist,它将包含 属性 的 名称 ,其值将紧随其后。

例如:

    <key>Title</key>
    <string>History</string>

使用 AppleScript 中的 XML 数据,我们将有两个 XML 元素:一个名为 "key",值为 "Title";另一个名为 "string",值为 "History"。对于 plist 数据,我们只有一个 属性 列表 key/value 对(项目),名称为 "Title",其值为 "History".

回到我最初的观点,确实有多个同名标签;同样,有多个 key/value 对具有相同的键,并且很可能具有相同的值。

因此,无论您将数据作为 XML 还是作为 属性 列表处理,总是 有必要告诉 AppleScript [=] 的哪个级别151=] 您正在引用的列表树。

AppleScripting

引用层次结构的哪一级完全取决于您要将书签存储在何处:在其自己的文件夹中;在收藏夹文件夹或其他已存在的文件夹中;或者仅在书签 collection 的 top-level 中,这样当您打开书签 side-bar.

时它会立即出现在列表中

为方便起见,我将演示最后一个选项:将书签添加到文件夹树的 top-level。我在下面标记了 plist 文件中要插入新条目的位置:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
    <plist version="1.0">
    <dict>
        <key>Children</key>
        <array>
            <dict>...</dict>
            <dict>...</dict>
            <dict>...</dict>
            <!-- etc... //-->
            <dict>...</dict>
            <!-- insert new entry here //-->
        </array>
        <!-- other stuff //-->
    </dict>
    </plist>

如果你运行这个命令:

    tell application "System Events" to ¬
        tell the property list file ¬
            "~/Library/Safari/Bookmarks.copy.plist" to ¬
            get the properties of the last property list item of ¬
                the property list item "Children"

AppleScript 将 return 一个 记录 ,这是它(较小的)尝试将数据存储为 key/value 对。它基本上是一个 AppleScript 列表,其中列表中的每个条目都有一个标签,用于引用该项目而不是索引号。输出 returns 看起来像这样(为了便于阅读而格式化):

     {value:{URIDictionary: ¬
         {title:"website.com"}, ¬
          previewText:"The title of the website", ¬
          ReadingListNonSync:{neverFetchMetadata:false}, ¬
          Sync:{ServerID:"DAV-DB576E16-E590-44D3-839F-63C0A1A8D3BC", ¬
               |data|:«a whole lot of data»}, ¬
          WebBookmarkUUID:"A5E4774A-2C26-4AD1-9938-D192C26860B8", ¬
          URLString:"https://website.com/path/etc", ¬
          WebBookmarkType:"WebBookmarkTypeLeaf", ¬
          previewTextIsUserDefined:true}, ¬
      kind:record, ¬
      class:property list item, ¬
      name:missing value, ¬
      text:"The XML representation of the property list data."}

这告诉我哪些项目构成了 plist 文件中的单个书签条目。请记住,从 AppleScript 的角度来看,这些项目中的每一个本身都是一个 property list item,因此需要单独创建。我假设我们可以忽略 WebBookmarkUUIDsync,它们都包含我们不可能自己生成的数据。不过,其余的都很简单。

创建新书签条目

    set urlAddr to "http://000.000.000.000:80"

    tell application "System Events" to tell ¬
        the property list file "~/Library/Safari/Bookmarks.plist" to tell ¬
        the property list item "Children" to tell ¬
        (make new property list item ¬
            at end of property list items ¬
            with properties {kind:record})

        make new property list item with properties ¬
            {name:"URIDictionary", kind:record}

        tell the result to make new property list item ¬
            with properties {name:"title", value:"mydomain.com", kind:string}

        make new property list item with properties ¬
            {name:"previewText", value:"Information about mydomain.com", kind:string}

        make new property list item with properties ¬
            {name:"URLString", value:urlAddr, kind:string}

        make new property list item with properties ¬
            {name:"WebBookmarkType", value:"WebBookmarkTypeLeaf", kind:string}

        make new property list item with properties ¬
            {name:"previewTextIsUserDefined", value:true, kind:boolean}

        make new property list item with properties ¬
            {name:"_tag", value:"AppleScript", kind:string}
    end tell

    return the result

运行 这在我的系统上成功地创建了一个名为 "mydomain.com" 的新书签条目,单击它时,它试图将我带到 URL“http://000.000.000.000” (根据您的 urlAddr 变量的虚拟值。

正在更新书签

要在每次 运行 脚本时更新它,您所要做的就是获得对书签的引用,然后是对包含有关 [ 的信息的 property list item 的引用=139=] 书签指向的位置。

您会注意到,在上面,我创建了一个名为 _tag 的 属性 列表项,其他书签条目没有。我这样做是为了通过简单地搜索包含 _tag:

的书签来轻松获得对书签的引用
    tell application "System Events" to tell ¬
        the property list file "~/Library/Safari/Bookmarks.copy.plist" to tell ¬
        the property list item "Children" to ¬
        set bookmark to ¬
            the first property list item whose ¬
            name of property list items contains "_tag"

随着书签引用存储在变量 bookmark 中,更新它指向的 URL 是这样完成的:

    tell application "System Events" to tell ¬
        the property list file "~/Library/Safari/Bookmarks.plist" to tell ¬
        the property list item "Children"

        set bookmark to ¬
            the first property list item whose ¬
            name of property list items contains "_tag"

        set URLString to the first property list item ¬
            of bookmark whose name is "URLString"

        set the value of URLString to urlAddr
    end tell

更新:

经过进一步测试并在通过 AppleScript 提交更改后返回检查 bookmarks.plist 文件,发现 Safari 自动 删除 user-defined 键 _tag,这是一种耻辱。相反,它插入 WebBookmarkUUID 并为其生成值,这很棒。

因此,很遗憾,我们无法使用上述方法来标记我们的书签条目,以便日后轻松参考。

因此,这个新脚本必然会更长。它旨在替换脚本中以 tell application "System Events".

开头的部分

它会先检查书签垫hing 指定的 title 存在于书签层次结构的顶层;如果没有,它将创建它;如果是,它将使用变量 urlAddr.

中包含的任何内容更新它指向的地址
    set urlAddr to "http://google.com"
    set myTitle to "mydomain.com"
    set plistFile to "~/Library/Safari/Bookmarks.plist"


    tell application "System Events"
        tell ¬
            the property list file plistFile to tell ¬
            the property list item "Children" to ¬
            set top_level to a reference to it


        set bookmarks to a reference to ¬
            (property list items of the top_level ¬
                whose name of property list items ¬
                contains "URLString")

        set labels to ¬
            a reference to property list item "title" of ¬
                property list item "URIDictionary" of ¬
                property list items of ¬
                the bookmarks


        if the value of the result ¬
            does not contain myTitle then ¬
            return saveBookmark of me ¬
                for urlAddr ¬
                to plistFile ¬
                at a reference to property list items of the top_level ¬
                given |title|:myTitle, previewText:"your description"


        repeat with i from the number of bookmarks to 1 by -1

            if item i of ¬
                the value of the labels is ¬
                myTitle then ¬
                exit repeat

        end repeat

        set bookmark to item i of bookmarks
        set the value of ¬
            property list item "URLString" of ¬
            the bookmark to ¬
            the urlAddr
    end tell


    to saveBookmark for www as string ¬
        at locationRef ¬
        to plist as string : "~/Library/Safari/Bookmarks.plist" given title:T ¬
        as string, previewText:_t as string : ""

        local www, locationRef, plist
        local T, _t


        tell application "System Events" to tell the property list file plist

            tell (make new property list item at end of locationRef ¬
                with properties {kind:record})

                tell (make new property list item with properties ¬
                    {name:"URIDictionary", kind:record}) to ¬
                    make new property list item with properties ¬
                        {name:"title", value:T, kind:string}

                make new property list item with properties ¬
                    {name:"previewText", value:_t}

                make new property list item with properties ¬
                    {name:"URLString", value:www}

                make new property list item with properties ¬
                    {name:"WebBookmarkType", value:"WebBookmarkTypeLeaf"}

                make new property list item with properties ¬
                    {name:"previewTextIsUserDefined", value:true}

            end tell
        end tell
    end saveBookmark

如果您的书签存在于书签层次结构中的其他地方(例如在文件夹中),目前将找不到它,并且将在顶层创建一个新书签。我将让您使用我在此示例脚本中演示的内容,并可能实现更深入的书签文件夹树遍历。

如果您有任何疑问或问题,请发表评论,我会尽快回复您。

考虑一下你的问题后,我决定采用不同的方法,不使用 AppleScript,直接在 bash [=290= 中完成所有操作] 脚本。此 脚本 可用于 Terminal(或 Automator service,以及留给用户实现的其他用途)。

以下内容仅作为概念验证,不一定经过稳健和/或完全编码以明确满足每个场景。您可以根据此处显示的 概念验证 自由使用它。

我在 macOS High Sierra[=189= 下测试了以下 example bash code ] 在 Terminal 中将其用作 bash shell script 并用作 Automator 服务没有任何意外问题。

这个脚本被编码为作用于包含NVR Camera的第一个书签,无论它存在于 Bookmarks.plist 文件的层次结构中。如果您有超过一个 bookmark 用于此 target,则额外的 bookmarks 用于相同的 target 不会更新。因为只有一个 bookmark 指向同一个 target 可能被认为是合理的,所以这应该不是问题。 (这并不是说它不能被编码来处理超过一个 bookmark 具有相同的 target,我只是没有写它方式!如果您需要,请随时这样做。)

无论 Safari 是否 运行ning,这个 script 都可以是 运行,因为它会动态地如果 Safari 是 运行ning,通过替换 Bookmarks.plist 文件进行更新。它甚至在 Bookmark Editor 打开的情况下进行了测试,可以看到 IP 地址 更新,好像这是原始现有信息中唯一更改的信息Bookmarks.plist 文件。换句话说,这是一个安全的过程。


作为普通的bashshell脚本,打开终端并执行以下命令:

touch UpdateIP; nano UpdateIP

nano中,复制并粘贴示例bash代码,如下图所示改变"google.com" 中的 h="google.com" 到正确的 主机名 以便 NVR Cameraping 命令一起使用 .

现在按以下键将 UpdateIP 文件保存在 nano 中:

  • 控制+X
  • Y
  • 输入

返回 命令行 ,使 UpdateIP 可执行:

chmod u+x UpdateIP

现在执行UpdateIP:

./UpdateIP

示例 bash 代码:

#!/bin/bash

set -e
# set -x

    # Host Name.

h="google.com"

    # Binary PLIST File.

bp="$HOME/Library/Safari/Bookmarks.plist"

    # XML File converted from Binary PLIST File.

bx="/tmp/Bookmarks.xml"

    # Get New Host IP Address.

nip="$(ping -c 1 -t 1 ${h} | grep -E -o -m 1 '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')"

    # Convert Binary PLIST File to XML File.

plutil -convert xml1 -o "${bx}" "${bp}"

    # Get Stored Host IP Address.

sip="$(grep -A3 -o 'NVR Camera' ${bx} | tail -1 | grep -E -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+')"

    # Compare New Host IP Address against Stored Host IP Address.
    # If they do not match, then update the 'Bookmark.plist' file.

if [[ ! $nip == "$sip" ]]; then

    printf '  Host IP Address Did Not Match!\n  Updating Host IP Address...\n'

        # Backup Binary PLIST File.

    cp "${bp}" "${bp}$(date +.%m.%d.%Y_%H.%M.%S)"

        # Get line number containing the Host IP Address.

    ln="$(awk '/NVR Camera/{print NR+3; exit}' ${bx})"

        # Update Host IP Address in XML File.

    eval "sed -i -e ${ln}s#${sip}#${nip}# ${bx}"

        # Convert updated XML File to overwrite existing Binary PLIST File.

    plutil -convert binary1 -o "${bp}" "${bx}"

else

    printf '  Host IP Address Matched!\n'

fi

注意:示例代码就是这样,没有使用任何明确的错误处理,尽管启用了 set -e 如果发生错误,它将终止执行,并且仅用于显示完成任务的多种方法之一。用户始终有责任 add/use 适当 错误处理 作为 needed/wanted。


来自UpdateIP 命令的以下终端输出显示为set -x 脚本 中启用,以便在 IP 地址 需要更新时显示它正在做什么:

$ ./UpdateIP
+ h=google.com
+ bp=/Users/me/Library/Safari/Bookmarks.plist
+ bx=/tmp/Bookmarks.xml
++ ping -c 1 -t 1 google.com
++ grep -E -o -m 1 '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
+ nip=172.217.4.46
+ plutil -convert xml1 -o /tmp/Bookmarks.xml /Users/me/Library/Safari/Bookmarks.plist
++ grep -A3 -o 'NVR Camera' /tmp/Bookmarks.xml
++ tail -1
++ grep -E -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
+ sip=172.217.30.20
+ [[ ! 172.217.4.46 == \.\.[=14=]\.[=14=] ]]
+ printf '  Host IP Address Did Not Match!\n  Updating Host IP Address...\n'
  Host IP Address Did Not Match!
  Updating Host IP Address...
++ date +.%m.%d.%Y_%H.%M.%S
+ cp /Users/me/Library/Safari/Bookmarks.plist /Users/me/Library/Safari/Bookmarks.plist.03.06.2018_16.50.00
++ awk '/NVR Camera/{print NR+3; exit}' /tmp/Bookmarks.xml
+ ln=98121
+ eval 'sed -i -e 98121s#172.217.30.20#172.217.4.46# /tmp/Bookmarks.xml'
++ sed -i -e 98121s#172.217.30.20#172.217.4.46# /tmp/Bookmarks.xml
+ plutil -convert binary1 -o /Users/me/Library/Safari/Bookmarks.plist /tmp/Bookmarks.xml
$ 

这里UpdateIP command 是 运行 之后直接显示 output 当没有更新时要求:

$ ./UpdateIP
+ h=google.com
+ bp=/Users/me/Library/Safari/Bookmarks.plist
+ bx=/tmp/Bookmarks.xml
++ ping -c 1 -t 1 google.com
++ grep -E -o -m 1 '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
+ nip=172.217.4.46
+ plutil -convert xml1 -o /tmp/Bookmarks.xml /Users/me/Library/Safari/Bookmarks.plist
++ grep -A3 -o 'NVR Camera' /tmp/Bookmarks.xml
++ tail -1
++ grep -E -o '[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+'
+ sip=172.217.4.46
+ [[ ! 172.217.4.46 == \.\.\. ]]
+ printf '  Host IP Address Matched!\n'
  Host IP Address Matched!
$ 

没有启用set -x输出应该分别显示如下:

$ ./UpdateIP
  Host IP Address Did Not Match!
  Updating Host IP Address...
$ 

并且:

$ ./UpdateIP
  Host IP Address Matched!
$ 

或者如果发生错误,例如ping 失败,或者 书签 不存在:

$ ./UpdateIP
$ 


作为 Automator service:

使用以下设置创建一个新的 Automator 服务

  • 服务在 [Safari] 中收到 [无输入]

添加一个运行Shell脚本动作,设置如下:

  • Shell [/bin/bash] 将输入传递给标准输入

用上面显示的示例bash代码替换默认代码 :

保存 Automator Service,然后在 Safari,select serviceSafari > Services 菜单,或者您可以为 service 添加键盘快捷方式: 系统偏好设置 > 键盘 > 快捷方式 > 服务 > 一般

请注意,作为 Automator 服务 ,如编码并使用 set -e,如果 ping 失败或目标 书签 不存在。由于我最初将其写为 bash shell script 以便在终端中使用,因此需要一些额外的编码才能在这些条件下不让 Automator 出错。如果发生任何一个错误,Bookmark.plist 文件都不会被替换,所以真的没有什么可担心的。