gpt4 book ai didi

python - 如何以编程方式创建和管理macOS Safari书签?

转载 作者:行者123 更新时间:2023-12-05 00:45:41 25 4
gpt4 key购买 nike

我正在制作一个脚本,该脚本将更新macOS Safari上的书签,以始终将所有订阅的子reddit作为单个书签保存在特定文件夹中。我已经到了要点,在Python中,所有子项都作为元组的排序列表,以所需的书签名称为第一个元素,以书签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书签文件夹并在该文件夹中创建这些新书签?

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

这是所需结果的图像,每个书签都链接到它们各自的subreddit url:

Bookmarks folder

最佳答案

tl; dr必须编辑Safari的Bookmarks.plist以编程方式创建书签。检出下面的“使用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,请运行:

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




物业清单

MacOS具有内置的与属性列表( .plist)相关的命令行工具,即 plutildefaults,可用于编辑通常包含平面数据结构的应用程序首选项。但是Safari的 Bookmarks.plist具有深层嵌套的结构,这两种工具都不擅长编辑。

.plist文件转换为XML

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

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


同样,以下命令将转换为二进制文件:

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


转换为XML可以使用 XSLT,它非常适合转换复杂的XML结构。



使用XSLT样式表

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

template.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需要用于指定每个必需书签属性的参数。


首先确保 Bookmarks.plits为XML格式:

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

利用内置的 xsltproctemplate.xsl应用于 Bookmarks.plist

首先,将 cd放置到 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


这将在您的 result-plist.xml上创建 Desktop,其中包含一个名为 QUUX的新书签文件夹,其中包含两个新书签。
让我们进一步了解上述复合命令中的每个部分:


uuidgen生成新 Bookmarks.plist中所需的三个UUID(一个用于文件夹,一个用于每个书签条目)。我们先生成它们,然后将它们传递给XSLT,因为:


XSLT 1.0没有用于UUID生成的功能。
xsltproc需要XSLT 1.0

xsltproc--stringparam选项表示自定义参数,如下所示:


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

每个书签规范均以逗号( ,)分隔。每个定界字符串具有三个值。书签的名称,URL和GUID。这3个值用空格分隔。
--stringparam guid <value>-书签文件夹的GUID。

最后部分:

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


定义路径; .xsl,源XML和目标。

要评估刚刚发生的转换,请使用 diff显示两个文件之间的差异。例如运行:

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


然后,按几次F键向前浏览到每一页,直到两列中间看到 >符号-它们指示在何处添加了新的元素节点。按B键后退一页,然后键入Q退出diff。




使用Bash脚本。

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

script.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提供以下功能:


简化的API,在通过Python执行时会很有用。
验证 .plist是否未损坏。
错误处理/记录。
使用内联的 .plist通过 xsltproc转换 template.xsl
根据编号创建要传递给XSLT的GUID。给定参数中指定的书签数量。
.plist转换为XML,然后转换回二进制。
将新文件写入操作系统的temp文件夹,然后将其移动到 Bookmarks.plist目录,从而有效地替换原始文件。


运行shell脚本


cd所在的 script.sh并运行以下 chmod命令以使 script.sh可执行:

chmod +ux script.sh

运行以下命令:

./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脚本

有几种方法可以通过您的 script.sh文件执行 .py

方法A:外部shell脚本

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

创建Safari浏览器bookmarks.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))


说明:


第一个 def语句定义一个 run-script函数。它有两个参数; folder_namebkmarkssubprocess模块 call方法本质上使用必需的参数执行 script.sh
第二个 def语句定义一个 tuple_to_shell_arg函数。它具有一个参数 tup。 String join()方法将元组列表转换为 script.sh所需的格式。本质上,它转换了一个元组列表,例如:

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


并返回一个字符串:

foo https://www.foo.com/,quux https://www.quux.com

run_script函数的调用方式如下:

run_script('subreddit', tuple_to_shell_arg(reddit_bkmarks))


这传递了两个参数。 subreddit(书签文件夹的名称),以及每个所需书签的规范(格式如先前在第2点中所述)。


运行 create-safari-bookmarks.py


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

chmod +ux ./create-safari-bookmarks.py

然后使用以下命令调用它:

./create-safari-bookmarks.py



方法B:内联shell脚本

根据您的确切用例,您可能需要考虑在 script.sh文件中内联 .py而不是调用外部 .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内容的最后一行之后的单独行中。
内联于 script.sh.py的第31行必须用另一个反斜杠转义 '%s\0'部分( \0为空字符),即 script.sh的第31行应如下所示:

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


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


创建野生动物园书签inlined.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))


说明


该文件获得与 create-safari-bookmarks.py相同的结果。
此修改后的 .py脚本包括一个修改后的 run_script函数,该函数利用Python的 tempfile模块将嵌入式Shell脚本保存到临时文件中。
然后,Python的 subprocess模块 call方法执行创建的临时外壳文件。


运行 create-safari-bookmarks-inlined.py


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

chmod +ux ./create-safari-bookmarks-inlined.py

然后通过运行来调用它:

./create-safari-bookmarks-inlined.py





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

当前,每次再次运行上述脚本/命令,我们都将使用一个全新的名称来有效地替换任何现有的命名Safari书签文件夹(名称与给定的书签文件夹名称相同),并创建一个新的书签。

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

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


默认值为 false。因此,如果我们要将 run_script中的 create-safari-bookmarks.py函数更改为以下内容。



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


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

然后,我们可以调用 run_script函数并传递一个附加的String参数,如 "true""false",如下所示:

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


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

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



MacOS Mojave限制

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

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


从Apple菜单中选择“系统偏好设置”。
在“系统偏好设置”窗口中,单击“安全和策略”图标。
在“安全和策略”窗格中,单击“隐私”选项卡。
在左侧列中选择“全盘访问”。
单击左下角的锁定图标以允许更改。
输入管理员密码,然后单击解锁按钮。
接下来,单击加号图标(+)。
选择位于 /Applications/Utilities/的Terminal.app,然后单击“打开”按钮。
Terminal.app将被添加到列表中。
单击锁定图标以防止进一步的更改,然后退出系统偏好设置。




screenshot

关于python - 如何以编程方式创建和管理macOS Safari书签?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56517403/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com