如何以编程方式创建和管理MacOSSafari书签?

2024-09-27 19:25:32 发布

您现在位置:Python中文网/ 问答频道 /正文

我正在制作一个脚本,它更新我的macossafari上的书签,使我所有订阅的子版块在一个特定的文件夹中作为单独的书签。在Python中,我已经将所有的subreddits作为元组的排序列表,第一个元素是想要的书签名称,第二个元素是bookmark url:

bookmarks = [
     ('r/Android', 'https://www.reddit.com/r/Android/'),
     ('r/Apple', 'https://www.reddit.com/r/Apple/'),
     ('r/Mac', 'https://www.reddit.com/r/Mac/'),
     ('r/ProgrammerHumor', 'https://www.reddit.com/r/ProgrammerHumor/')
]

如何清除Safari中的subreddit bookmarks文件夹并在该文件夹中创建这些新书签?在

到目前为止,我一直使用Python,但是从Python程序调用外部AppleScript或Shell脚本没有问题。在

下面是想要的结果的图像,每个书签都链接到各自的subreddit url:

Bookmarks folder


Tags: https脚本文件夹comurl元素applemac
2条回答
<>强>太长了,读不下去了,需要编辑Safari的^ {CD1>}以编程方式创建书签。查看下面的“使用Python脚本”部分。它需要在Bash脚本中使用XSLT样式表,并通过.py文件调用它。实现这一点所需的所有工具都内置在macOS上。在

重要提示:使用macOS Mojave(10.14.x)+您需要执行下面“macOS Mojave限制”部分中的步骤1-10。这些更改允许对Bookmarks.plist进行修改。

在继续之前,请创建Bookmarks.plist的副本,该副本位于~/Library/Safari/Bookmarks.plist。您可以运行以下命令将其复制到您的桌面

cp ~/Library/Safari/Bookmarks.plist ~/Desktop/Bookmarks.plist

要在以后还原Bookmarks.plist,请运行:

^{pr2}$

属性列表

MacOS有内置的属性列表(.plist)相关的命令行工具,即^{},和^{},它们有助于编辑通常包含平面数据结构的应用程序首选项。然而,Safari的Bookmarks.plist有一个深度嵌套的结构,这两个工具都不擅长编辑。在

.plist文件转换为XML

plutil提供了一个-convert选项来将.plist从二进制转换为XML。例如:

^{3}$

类似地,以下命令将转换为二进制:

plutil -convert binary1 ~/Library/Safari/Bookmarks.plist

转换为XML可以使用XSLT,这是转换复杂XML结构的理想选择。在


使用XSLT样式表

此自定义XSLT样式表转换Bookmarks.plist添加元素节点以创建书签:

模板.xsl

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:strip-space elements="*"/>
  <xsl:output
    method="xml"
    indent="yes"
    doctype-system="http://www.apple.com/DTDs/PropertyList-1.0.dtd"
    doctype-public="-//Apple//DTD PLIST 1.0//EN"/>

  <xsl:param name="bkmarks-folder"/>
  <xsl:param name="bkmarks"/>
  <xsl:param name="guid"/>
  <xsl:param name="keep-existing" select="false" />

  <xsl:variable name="bmCount">
    <xsl:value-of select="string-length($bkmarks) -
          string-length(translate($bkmarks, ',', '')) + 1"/>
  </xsl:variable>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template name="getNthValue">
    <xsl:param name="list"/>
    <xsl:param name="n"/>
    <xsl:param name="delimiter"/>

    <xsl:choose>
      <xsl:when test="$n = 1">
        <xsl:value-of select=
              "substring-before(concat($list, $delimiter), $delimiter)"/>
      </xsl:when>
      <xsl:when test="contains($list, $delimiter) and $n > 1">
        <!  recursive call  >
        <xsl:call-template name="getNthValue">
          <xsl:with-param name="list"
              select="substring-after($list, $delimiter)"/>
          <xsl:with-param name="n" select="$n - 1"/>
          <xsl:with-param name="delimiter" select="$delimiter"/>
        </xsl:call-template>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="createBmEntryFragment">
    <xsl:param name="loopCount" select="1"/>

    <xsl:variable name="bmInfo">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bkmarks"/>
        <xsl:with-param name="delimiter" select="','"/>
        <xsl:with-param name="n" select="$loopCount"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmkName">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="1"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmURL">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="2"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmGUID">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="3"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:if test="$loopCount > 0">
      <dict>
        <key>ReadingListNonSync</key>
        <dict>
          <key>neverFetchMetadata</key>
          <false/>
        </dict>
        <key>URIDictionary</key>
        <dict>
          <key>title</key>
          <string>
            <xsl:value-of select="$bmkName"/>
          </string>
        </dict>
        <key>URLString</key>
        <string>
          <xsl:value-of select="$bmURL"/>
        </string>
        <key>WebBookmarkType</key>
        <string>WebBookmarkTypeLeaf</string>
        <key>WebBookmarkUUID</key>
        <string>
          <xsl:value-of select="$bmGUID"/>
        </string>
      </dict>
      <!  recursive call  >
      <xsl:call-template name="createBmEntryFragment">
        <xsl:with-param name="loopCount" select="$loopCount - 1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="createBmFolderFragment">
    <dict>
      <key>Children</key>
      <array>
        <xsl:call-template name="createBmEntryFragment">
          <xsl:with-param name="loopCount" select="$bmCount"/>
        </xsl:call-template>
        <xsl:if test="$keep-existing = 'true'">
          <xsl:copy-of select="./array/node()|@*"/>
        </xsl:if>
      </array>
      <key>Title</key>
      <string>
        <xsl:value-of select="$bkmarks-folder"/>
      </string>
      <key>WebBookmarkType</key>
      <string>WebBookmarkTypeList</string>
      <key>WebBookmarkUUID</key>
      <string>
        <xsl:value-of select="$guid"/>
      </string>
    </dict>
  </xsl:template>

  <xsl:template match="dict[string[text()='BookmarksBar']]/array">
    <array>
      <xsl:for-each select="dict">
        <xsl:choose>
          <xsl:when test="string[text()=$bkmarks-folder]">
            <xsl:call-template name="createBmFolderFragment"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy>
              <xsl:apply-templates select="@*|node()" />
            </xsl:copy>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>

      <xsl:if test="not(./dict/string[text()=$bkmarks-folder])">
        <xsl:call-template name="createBmFolderFragment"/>
      </xsl:if>
    </array>
  </xsl:template>

</xsl:stylesheet>

运行转换:

.xsl需要指定每个所需书签属性的参数。在

  1. 首先确保Bookmarks.plits是XML格式的:

    plutil -convert xml1 ~/Library/Safari/Bookmarks.plist
    
  2. 利用内置的^{}template.xsl应用到Bookmarks.plist。在

    首先,^{}template.xsl所在的位置,然后运行以下复合命令:

    guid1=$(uuidgen) && guid2=$(uuidgen) && guid3=$(uuidgen) && xsltproc  novalid  stringparam bkmarks-folder "QUUX"  stringparam bkmarks "r/Android https://www.reddit.com/r/Android/ ${guid1},r/Apple https://www.reddit.com/r/Apple/ ${guid2}"  stringparam guid "$guid3" ./template.xsl - <~/Library/Safari/Bookmarks.plist > ~/Desktop/result-plist.xml
    

    这将在您的Desktop上创建result-plist.xml,其中包含一个名为QUUX的新书签文件夹,其中包含两个新书签。

  3. 让我们进一步了解上述复合命令中的每个部分:

    • ^{}生成新的Bookmarks.plist中需要的三个UUID(一个用于文件夹,一个用于每个书签条目)。我们预先生成它们并将它们传递给XSLT,因为:

      • XSLT1.0没有生成UUID的功能。在
      • xsltproc需要XSLT1.0
    • xsltproc stringparam选项表示如下自定义参数:

      • stringparam bkmarks-folder <value>-书签文件夹的名称。在
      • {cd33}每个书签的属性。在

        每个书签规范都用逗号(,)分隔。每个分隔字符串有三个值:书签名称、URL和GUID。这3个值以空格分隔。

      • stringparam guid <value>-书签文件夹的GUID。

    • 最后一部分:

      ./template.xsl - <~/Library/Safari/Bookmarks.plist > ~/Desktop/result-plist.xml
      

      定义到的路径;.xsl、源XML和目标。

  4. 要计算刚刚发生的转换,请使用^{}来显示两个文件之间的差异。例如运行:

    diff -yb  width 200 ~/Library/Safari/Bookmarks.plist ~/Desktop/result-plist.xml | less
    

    然后按几次F键向前导航到每一页,直到在两列中间看到>符号-它们指示添加新元素节点的位置。按B键向后移动一页,然后键入Q退出diff.


使用Bash脚本

我们现在可以在Bash脚本中使用前面提到的.xsl。在

脚本.sh

#!/usr/bin/env bash

declare -r plist_path=~/Library/Safari/Bookmarks.plist

# ANSI/VT100 Control sequences for colored error log.
declare -r fmt_red='\x1b[31m'
declare -r fmt_norm='\x1b[0m'
declare -r fmt_green='\x1b[32m'
declare -r fmt_bg_black='\x1b[40m'

declare -r error_badge="${fmt_red}${fmt_bg_black}ERR!${fmt_norm}"
declare -r tick_symbol="${fmt_green}\\xE2\\x9C\\x94${fmt_norm}"

if [ -z "$1" ] || [ -z "$2" ]; then
  echo -e "${error_badge} Missing required arguments" >&2
  exit 1
fi

bkmarks_folder_name=$1
bkmarks_spec=$2

keep_existing_bkmarks=${3:-false}

# Transform bookmark spec string into array using comma `,` as delimiter.
IFS=',' read -r -a bkmarks_spec <<< "${bkmarks_spec//, /,}"

# Append UUID/GUID to each bookmark spec element.
bkmarks_spec_with_uuid=()
while read -rd ''; do
  [[ $REPLY ]] && bkmarks_spec_with_uuid+=("${REPLY} $(uuidgen)")
done < <(printf '%s\0' "${bkmarks_spec[@]}")

# Transform bookmark spec array back to string using comma `,` as delimiter.
bkmarks_spec_str=$(printf '%s,' "${bkmarks_spec_with_uuid[@]}")
bkmarks_spec_str=${bkmarks_spec_str%,} # Omit trailing comma character.

# Check the .plist file exists.
if [ ! -f "$plist_path" ]; then
  echo -e "${error_badge} File not found: ${plist_path}" >&2
  exit 1
fi

# Verify that plist exists and contains no syntax errors.
if ! plutil -lint -s "$plist_path" >/dev/null; then
  echo -e "${error_badge} Broken or missing plist: ${plist_path}" >&2
  exit 1
fi

# Ignore ShellCheck errors regarding XSLT variable references in template below.
# shellcheck disable=SC2154
xslt() {
cat <<'EOX'
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

  <xsl:strip-space elements="*"/>
  <xsl:output
    method="xml"
    indent="yes"
    doctype-system="http://www.apple.com/DTDs/PropertyList-1.0.dtd"
    doctype-public="-//Apple//DTD PLIST 1.0//EN"/>

  <xsl:param name="bkmarks-folder"/>
  <xsl:param name="bkmarks"/>
  <xsl:param name="guid"/>
  <xsl:param name="keep-existing" select="false" />

  <xsl:variable name="bmCount">
    <xsl:value-of select="string-length($bkmarks) -
          string-length(translate($bkmarks, ',', '')) + 1"/>
  </xsl:variable>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template name="getNthValue">
    <xsl:param name="list"/>
    <xsl:param name="n"/>
    <xsl:param name="delimiter"/>

    <xsl:choose>
      <xsl:when test="$n = 1">
        <xsl:value-of select=
              "substring-before(concat($list, $delimiter), $delimiter)"/>
      </xsl:when>
      <xsl:when test="contains($list, $delimiter) and $n > 1">
        <!  recursive call  >
        <xsl:call-template name="getNthValue">
          <xsl:with-param name="list"
              select="substring-after($list, $delimiter)"/>
          <xsl:with-param name="n" select="$n - 1"/>
          <xsl:with-param name="delimiter" select="$delimiter"/>
        </xsl:call-template>
      </xsl:when>
    </xsl:choose>
  </xsl:template>

  <xsl:template name="createBmEntryFragment">
    <xsl:param name="loopCount" select="1"/>

    <xsl:variable name="bmInfo">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bkmarks"/>
        <xsl:with-param name="delimiter" select="','"/>
        <xsl:with-param name="n" select="$loopCount"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmkName">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="1"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmURL">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="2"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:variable name="bmGUID">
      <xsl:call-template name="getNthValue">
        <xsl:with-param name="list" select="$bmInfo"/>
        <xsl:with-param name="delimiter" select="' '"/>
        <xsl:with-param name="n" select="3"/>
      </xsl:call-template>
    </xsl:variable>

    <xsl:if test="$loopCount > 0">
      <dict>
        <key>ReadingListNonSync</key>
        <dict>
          <key>neverFetchMetadata</key>
          <false/>
        </dict>
        <key>URIDictionary</key>
        <dict>
          <key>title</key>
          <string>
            <xsl:value-of select="$bmkName"/>
          </string>
        </dict>
        <key>URLString</key>
        <string>
          <xsl:value-of select="$bmURL"/>
        </string>
        <key>WebBookmarkType</key>
        <string>WebBookmarkTypeLeaf</string>
        <key>WebBookmarkUUID</key>
        <string>
          <xsl:value-of select="$bmGUID"/>
        </string>
      </dict>
      <!  recursive call  >
      <xsl:call-template name="createBmEntryFragment">
        <xsl:with-param name="loopCount" select="$loopCount - 1"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:template name="createBmFolderFragment">
    <dict>
      <key>Children</key>
      <array>
        <xsl:call-template name="createBmEntryFragment">
          <xsl:with-param name="loopCount" select="$bmCount"/>
        </xsl:call-template>
        <xsl:if test="$keep-existing = 'true'">
          <xsl:copy-of select="./array/node()|@*"/>
        </xsl:if>
      </array>
      <key>Title</key>
      <string>
        <xsl:value-of select="$bkmarks-folder"/>
      </string>
      <key>WebBookmarkType</key>
      <string>WebBookmarkTypeList</string>
      <key>WebBookmarkUUID</key>
      <string>
        <xsl:value-of select="$guid"/>
      </string>
    </dict>
  </xsl:template>

  <xsl:template match="dict[string[text()='BookmarksBar']]/array">
    <array>
      <xsl:for-each select="dict">
        <xsl:choose>
          <xsl:when test="string[text()=$bkmarks-folder]">
            <xsl:call-template name="createBmFolderFragment"/>
          </xsl:when>
          <xsl:otherwise>
            <xsl:copy>
              <xsl:apply-templates select="@*|node()" />
            </xsl:copy>
          </xsl:otherwise>
        </xsl:choose>
      </xsl:for-each>

      <xsl:if test="not(./dict/string[text()=$bkmarks-folder])">
        <xsl:call-template name="createBmFolderFragment"/>
      </xsl:if>
    </array>
  </xsl:template>

</xsl:stylesheet>
EOX
}

# Convert the .plist to XML format
plutil -convert xml1   "$plist_path" >/dev/null || {
  echo -e "${error_badge} Cannot convert .plist to xml format" >&2
  exit 1
}

# Generate a UUID/GUID for the folder.
folder_guid=$(uuidgen)

xsltproc  novalid \
     stringparam keep-existing "$keep_existing_bkmarks" \
     stringparam bkmarks-folder "$bkmarks_folder_name" \
     stringparam bkmarks "$bkmarks_spec_str" \
     stringparam guid "$folder_guid" \
    <(xslt) - <"$plist_path" > "${TMPDIR}result-plist.xml"

# Convert the .plist to binary format
plutil -convert binary1   "${TMPDIR}result-plist.xml" >/dev/null || {
  echo -e "${error_badge} Cannot convert .plist to binary format" >&2
  exit 1
}

mv   "${TMPDIR}result-plist.xml" "$plist_path" 2>/dev/null || {
  echo -e "${error_badge} Cannot move .plist from TMPDIR to ${plist_path}" >&2
  exit 1
}

echo -e "${tick_symbol} Successfully created Safari bookmarks."

解释

script.sh提供以下功能:

  1. 简化的API在通过Python执行时非常有用。在
  2. 验证.plist未损坏。在
  3. 错误处理记录/记录。在
  4. 使用template.xsl内联,通过xsltproc转换{}。在
  5. 根据给定参数中指定的书签数创建用于传递给XSLT的GUID。在
  6. .plist转换为XML,然后再转换回二进制。在
  7. 将新文件写入操作系统的temp文件夹,然后将其移动到Bookmarks.plist目录,有效地替换原始文件。在

运行shell脚本

  1. cdscript.sh所在的位置,并运行以下^{}命令使script.sh可执行:

    chmod +ux script.sh
    
  2. 运行以下命令:

    ./script.sh "stackOverflow" "bash https://stackoverflow.com/questions/tagged/bash,python https://stackoverflow.com/questions/tagged/python"
    

    然后将以下内容打印到CLI:

    ✔ Successfully created Safari bookmarks.

    Safari现在有一个名为stackOverflow的书签文件夹,其中包含两个书签(bashpython)。


使用Python脚本

有两种方法可以通过.py文件执行script.sh。在

方法A:外部shell脚本

下面的.py文件执行外部script.sh文件。让我们将文件命名为create-safari-bookmarks.py,并将其与script.sh保存在同一个文件夹中。在

创建safari-书签.py

#!/usr/bin/env python

import subprocess


def run_script(folder_name, bkmarks):
    subprocess.call(["./script.sh", folder_name, bkmarks])


def tuple_to_shell_arg(tup):
    return ",".join("%s %s" % t for t in tup)

reddit_bkmarks = [
    ('r/Android', 'https://www.reddit.com/r/Android/'),
    ('r/Apple', 'https://www.reddit.com/r/Apple/'),
    ('r/Mac', 'https://www.reddit.com/r/Mac/'),
    ('r/ProgrammerHumor', 'https://www.reddit.com/r/ProgrammerHumor/'),
    ('r/gaming', 'https://www.reddit.com/r/gaming/')
]

so_bkmarks = [
    ('bash', 'https://stackoverflow.com/questions/tagged/bash'),
    ('python', 'https://stackoverflow.com/questions/tagged/python'),
    ('xslt', 'https://stackoverflow.com/questions/tagged/xslt'),
    ('xml', 'https://stackoverflow.com/questions/tagged/xml')
]

run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))
run_script("stackOverflow", tuple_to_shell_arg(so_bkmarks))

说明:

  1. 第一个def语句定义了一个run-script函数。它有两个参数:folder_namebkmarkssubprocess模块call方法本质上使用所需参数执行script.sh

  2. 第二条def语句定义了一个tuple_to_shell_arg函数。它有一个参数tup。Stringjoin()方法将元组列表转换为script.sh所需的格式。它本质上转换元组列表,例如:

    [
        ('foo', 'https://www.foo.com/'),
        ('quux', 'https://www.quux.com')
    ]
    

    并返回一个字符串:

    foo https://www.foo.com/,quux https://www.quux.com
    
  3. 按如下方式调用run_script函数:

    run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))
    

    这将传递两个参数;subreddit(bookmarks文件夹的名称)和每个所需书签的规范(格式如前面第2点所述)。

正在运行create-safari-bookmarks.py

  1. 使create-safari-bookmarks.py可执行:

    chmod +ux ./create-safari-bookmarks.py
    
  2. 然后调用它:

    ./create-safari-bookmarks.py
    

方法B:内联shell脚本

根据具体的用例,您可能需要考虑在.py文件中内联script.sh,而不是调用外部.sh文件。让我们将这个文件命名为create-safari-bookmarks-inlined.py,并将其保存到create-safari-bookmarks.py所在的同一个目录中。在

重要:

  • 您需要将script.sh中的所有内容复制并粘贴到create-safari-bookmarks-inlined.py中指定的位置。

  • 将其粘贴到bash_script = """\部分后面的下一行。

  • create-safari-bookmarks-inlined.py中的"""部分应该在粘贴的script.sh内容的最后一行后面的自己的行上。在
  • 当在.py中内联时,script.sh的第31行必须使用另一个反斜杠转义'%s\0'部分(\0是空字符),即{}的第31行应该如下所示:

    ...
    done < <(printf '%s\\0' "${bkmarks_spec[@]}")
                       ^
    ...
    

    这一行可能在create-safari-bookmarks-inlined.py中的第37行。

创建safari书签-内联.py

#!/usr/bin/env python

import tempfile
import subprocess

bash_script = """\
# < - Copy and paste content of `script.sh` here and modify its line 31.
"""


def run_script(script, folder_name, bkmarks):
    with tempfile.NamedTemporaryFile() as scriptfile:
        scriptfile.write(script)
        scriptfile.flush()
        subprocess.call(["/bin/bash", scriptfile.name, folder_name, bkmarks])


def tuple_to_shell_arg(tup):
    return ",".join("%s %s" % t for t in tup)

reddit_bkmarks = [
    ('r/Android', 'https://www.reddit.com/r/Android/'),
    ('r/Apple', 'https://www.reddit.com/r/Apple/'),
    ('r/Mac', 'https://www.reddit.com/r/Mac/'),
    ('r/ProgrammerHumor', 'https://www.reddit.com/r/ProgrammerHumor/'),
    ('r/gaming', 'https://www.reddit.com/r/gaming/')
]

so_bkmarks = [
    ('bash', 'https://stackoverflow.com/questions/tagged/bash'),
    ('python', 'https://stackoverflow.com/questions/tagged/python'),
    ('xslt', 'https://stackoverflow.com/questions/tagged/xslt'),
    ('xml', 'https://stackoverflow.com/questions/tagged/xml')
]

run_script(bash_script, "subreddit", tuple_to_shell_arg(reddit_bkmarks))
run_script(bash_script, "stackOverflow", tuple_to_shell_arg(so_bkmarks))

说明

  1. 此文件的结果与create-safari-bookmarks.py相同。

  2. 这个修改过的.py脚本包含一个修改后的run_script函数,该函数利用Python的tempfile模块将内联shell脚本保存到临时文件中。

  3. Python的subprocess模块call方法随后执行临时创建的shell文件。

正在运行create-safari-bookmarks-inlined.py

  1. 使create-safari-bookmarks-inlined.py可执行:

    chmod +ux ./create-safari-bookmarks-inlined.py
    
  2. 然后通过运行以下命令调用它:

    ./create-safari-bookmarks-inlined.py
    

附加说明:将书签附加到现有文件夹

目前,每次再次运行上述脚本/命令时,我们都在有效地替换任何现有的命名Safari bookmark folder(它与给定的bookmark folder同名),使用一个全新的bookmark folder创建指定的书签。在

但是,如果您想将书签附加到现有文件夹,则template.xsl包含一个要传递给它的附加参数/参数。请注意第14行的部分内容:

<xsl:param name="keep-existing" select="false" />

它的默认值是false。所以,如果我们要将run_script中的run_script函数改为如下。在

def run_script(folder_name, bkmarks, keep_existing):
        subprocess.call(["./script.sh", folder_name, bkmarks, keep_existing])

也就是说,添加一个名为keep_existing的第三个参数,并在subprocess.call([...])中包含对它的引用,即将其作为第三个参数传递给script.sh(…并随后传递到XSLT样式表)。在

{{cd110>然后我们可以调用一个额外的{cd110}函数

run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks), "true")
run_script("stackOverflow", tuple_to_shell_arg(so_bkmarks), "false")

但是,进行上述更改(即传入"true"以保留现有书签),确实有可能导致创建重复的书签。例如,当我们有一个退出的书签(名称和URL)时,就会出现重复的书签,然后在以后用相同的名称和URL重新提供该书签。在

限制:当前为书签提供的任何名称参数都不能包含空格字符,因为它们被脚本用作分隔符。在


马科斯莫哈韦限制

由于macOS上更严格的安全策略,默认情况下不允许访问~/Library/Safari/Bookmarks.plist(如this answer中所述)。在

因此,有必要授予终端.app(或其他首选的CLI工具,如iTerm)访问整个磁盘。为此,您需要:

  1. 从Apple菜单中选择系统首选项。在
  2. 系统首选项窗口中,单击安全和策略图标。在
  3. 安全和策略窗格中,单击隐私选项卡。在
  4. 在左侧栏中选择完整磁盘访问。在
  5. 单击左下角的锁定图标以允许更改。在
  6. 输入管理员密码,然后单击解锁按钮。在
  7. 接下来单击加号图标(+)。在
  8. 选择终端.app,可位于/Applications/Utilities/,然后单击打开按钮。在
  9. 终端.app将被添加到列表中。在
  10. 单击锁定图标以阻止任何进一步的更改,然后退出系统首选项。在

{a10}

我从来没有在Safari(而不是AS字典)中找到管理书签的AS命令。所以我建立了自己的例程来处理Safari bookmark plist文件。然而,它们会受到苹果公司在未来书签处理方式上的意外变化的影响!到目前为止,它仍然在工作,但我还没有使用10.14

首先,您必须获取这个plist文件才能更改它。这个部分必须在你的主代码中。它为您提供了plist文件的修补程序:

 set D_Lib to ((path to library folder from user domain) as string) & "Safari"
 set SafariPlistFile to D_Lib & ":Bookmarks.plist"

这里有两个子例程来管理书签。第一个检查书签是否存在

^{pr2}$

您可以将此处理程序称为如下:

^{3}$

第二个处理程序创建一个新书签:

on New_BM(FPlist, BM_Name, N_URL)   create new bookmark at right end side of bookmarks and return its number
    tell application "System Events"
        set Main_Bar to property list item "Children" of property list item 2 of property list item "Children" of property list file FPlist
        set numBM to count of property list item of Main_Bar
        tell Main_Bar
            set my_UUID to do shell script "uuidgen"   create unique Apple UID
            set myNewBM to make new property list item at the end with properties {kind:record}
            tell myNewBM
                set URIDict to make new property list item with properties {kind:record, name:"URIDictionary"}
                tell URIDict to make new property list item with properties {name:"title", kind:string, value:BM_Name}
                make new property list item with properties {name:"URLString", kind:string, value:N_URL}
                make new property list item with properties {name:"WebBookmarkType", kind:string, value:"WebBookmarkTypeLeaf"}
                make new property list item with properties {name:"WebBookmarkUUID", kind:string, value:my_UUID}
            end tell   myNewBM
        end tell
    end tell
    return (numBM + 1)
end New_BM

我使用这些程序来添加、检查和更改书签右侧的书签。在您的例子中,您需要使用书签子菜单,然后您必须调整此代码,但主要概念是相同的。在

为了方便起见,我建议您开始查找plist文件(Library/Safari/书签.plist)当你的书签在子菜单中时,可以看到它的结构。在

我希望这有帮助!在

相关问题 更多 >

    热门问题