XML 在 OS X 终端中解析 MobileConfig 文件

XML Parsing in OS X Terminal for MobileConfig file

我正在通过 bash 脚本生成(实际上是编辑)mobileconfig 文件(又名 iOS 配置文件,XML)。

脚本从 MS 数据库获取数据,现在 inject/replace 我的 mobileconfig 文件中的数据 (XML)。

XML 文件具有以下结构:

<?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>PayloadContent</key>
    <array>
        <dict>
            <key>Host</key>
            <string>outlook.office365.com</string>
            <key>MailNumberOfPastDaysToSync</key>
            <integer>7</integer>
            <key>Password</key>
            <string>ActiveSyncPassword</string>
            <key>PayloadDescription</key>
            <string>Configures an Exchange account</string>
            <key>PayloadDisplayName</key>
            <string>Exchange ActiveSync</string>
            <key>PayloadVersion</key>
            <integer>1</integer>
            <key>SSL</key>
            <true/>
            <key>UserName</key>
            <string>xxxxxxx@xxx.com</string>
            <key>disableMailRecentsSyncing</key>
            <false/>
        </dict>
        <dict>
            <key>AutoJoin</key>
            <true/>
            <key>EncryptionType</key>
            <string>WPA</string>
            <key>HIDDEN_NETWORK</key>
            <true/>
            <key>IsHotspot</key>
            <false/>
            <key>Password</key>
            <string>WEPWPAWPSPEAPTLS</string>
            <key>PayloadType</key>
            <string>com.apple.wifi.managed</string>
            <key>PayloadVersion</key>
            <real>1</real>
            <key>ProxyType</key>
            <string>None</string>
            <key>SSID_STR</key>
            <string>SSID</string>
        </dict>
        <dict>

我想使用任何本机(xmllint、sed)或非本机工具替换 WiFi 密码以及 之间的 ActiveSync "Password" 字段。

有人可以帮忙吗?

你可以这样做

sed -r "s#(<string>)SSID_STR(</string>)#AMD#g" File

就地替换:

sed -i -r "s#(<string>)SSID_STR(</string>)#AMD#g" File

()用于分组。 </code>和<code>代表firstsecond这样的组。 将 AMD 替换为您的实际内容。同样,你可以为密码做。

使用纯文本工具编辑结构化数据(例如 XML)时,当文件格式以无人期望的方式改变(例如插入良性空白)时,总是会以痛苦告终。相反,使用可以正确解析 XML 并在树上工作的工具,例如 xmlstarlet.

一般形式是

xmlstarlet ed -u xpath -v value filename.xml

其中 xpath 是一个 XPath 表达式,用于标识要更新的节点,value 是您要赋予它的新值。神奇之处在于构造一个 XPath 表达式,该表达式唯一且可靠地标识您要更新的节点。 MobileConfig XML 格式使这比平常更难;在评论中讨论后,我们最终得出

xmlstarlet ed -u '//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]/key[text() = "Password"]/following-sibling::string[1]' -v 'abc123' filename.xml

这里的核心是XPath表达式

//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]/key[text() = "Password"]/following-sibling::string[1]

..这需要一些解释。我们使用以下功能:

  • //dict 匹配文档中的任何 dict 节点,
  • //dict/key 匹配作为 dict 节点子节点的任何 key 节点,
  • //dict/key[text() = "Password"] 匹配作为 dict 节点子节点且包含文本 Password
  • 的任何 key 注释
  • //dict/key[text() = "Password"]/following-sibling 匹配此类 key 节点的任何后续兄弟节点,也就是说任何作为同一父节点的子节点且位于 key 节点之后的任何节点XML,
  • //dict/key[text() = "Password"]/following-sibling::string 匹配任何 string 节点是这样的后续兄弟节点,并且
  • //dict/key[text() = "Password"]/following-sibling::string[1] 匹配作为此类 key 节点的第一个后续兄弟 string 节点的任何节点。

我们已经在//dict/key[text() = "Password"]中使用了一个条件;为了找到要更改其密码条目的 dict 节点,我们需要更多。我们要查找的 dict 节点由

标识
//dict[key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"]

即满足条件dict的节点

key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"

此条件下的 XPath 表达式都与正在测试的 dict 节点相关,因此

key[text() = "PayloadDisplayName"]

指的是包含文本 PayloadDisplayNamedict 节点的 key 子节点,并且

key[text() = "PayloadDisplayName"]/following-sibling::string[1] = "Exchange ActiveSync"
如果包含文本 PayloadDisplayNamekey 节点后面的 string 节点中的文本是 Exchange ActiveSync,则

为真。所以我们将其放入我上面解释的简化表达式中并获得完整的过滤器。

我觉得有必要指出这个 XML 文件的结构使整个事情比必要的或通常的更困难。合理的结构 XML 可以用更简单的 XPath 表达式处理(大多数情况下)。