重塑 json / 修复 shell 脚本内的 json (删除尾随逗号)

重塑 json / 修复 shell 脚本内的 json (删除尾随逗号)

我已经为此进行了很多搜索,似乎没有这种需求的先兆。

我需要以编程方式编辑应用程序首选项文件:作为 shell 脚本的一部分。

并且首选项以严格的 json 格式存储:这意味着如果,右大括号之前有逗号,则加载该首选项文件的应用程序将在启动时崩溃}

通常这不会是一个问题。

我只是sed相应地使用我的 s :如果包含错误文本的行在我的示例文件中的一个部分的末尾排列,那么在替换此文本时我将总是不加逗号。

如果包含我想要替换的另一个故障位的另一行不在末尾,我总是替换它,包括逗号。

例子 :

(我使用下划线_作为 sed 的分隔符,因为要替换的字符串有时充满反斜杠)

sed -i 's_"executableDecorator".*_"executableDecorator": "'$user_path'/faf/run \\"%s\\"",_' $user_path/.faforever/client.prefs

如果该行位于末尾:

sed -i 's_"executableDecorator".*_"executableDecorator": "'$user_path'/faf/run \\"%s\\""_' $user_path/.faforever/client.prefs

工作,但是!...

在运行脚本之前,我已经结束了应用程序,这样两者就不会同时编辑首选项,但即使如此,由于该应用程序的异步执行,我的脚本每次收到的首选项都会有所不同。

这是完全随机的。

有时一条线可能在中间,有时在最后。应用程序本身(Java 和一些 json java lib)知道如何根据上下文附加逗号或不附加逗号,但作为我的 shell 脚本的一部分......我觉得事情会变得臃肿。

(如果没有,并且有一个简写来确保我有逗号或没有逗号,具体取决于下一行是否是},那么这是一个更好的更简单的解决方案,我会更感兴趣)

但就目前情况而言,我正在寻找一个修复 json 的 POSIX 实用程序,以便在我完成 shell 脚本中的所有操作后,我可以“清理”我的 json prefs 文件……这样的东西存在吗?

编辑 :

这是基本文件(整个文件):

{
  "mainWindow": {
    "width": 800,
    "height": 600,
    "maximized": false,
    "lastView": "NEWS",
    "lastChildViews": {},
    "x": 67.0,
    "y": 27.0
  },
  "forgedAlliance": {
    "customMapsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Maps",
    "preferencesFile": "/home/t/.wine/drive_c/users/t/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs",
    "officialMapsDirectory": "/home/t/faf/./Maps",
    "modsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Mods",
    "port": 6112,
    "autoDownloadMaps": true,
    "executableDecorator": "\"%s\""
  },
  "login": {
    "username": "tatsu",
    "password": "*******",
    "autoLogin": true
  },
  "chat": {
    "zoom": 1.0,
    "learnedAutoComplete": false,
    "previewImageUrls": true,
    "maxMessages": 500,
    "chatColorMode": "CUSTOM",
    "channelTabScrollPaneWidth": 250,
    "userToColor": {},
    "hideFoeMessages": true,
    "timeFormat": "AUTO",
    "chatFormat": "COMPACT",
    "idleThreshold": 10
  },
  "notification": {
    "soundsEnabled": true,
    "transientNotificationsEnabled": true,
    "mentionSoundEnabled": true,
    "infoSoundEnabled": true,
    "warnSoundEnabled": true,
    "errorSoundEnabled": true,
    "friendOnlineToastEnabled": true,
    "friendOfflineToastEnabled": true,
    "ladder1v1ToastEnabled": true,
    "friendOnlineSoundEnabled": true,
    "friendOfflineSoundEnabled": true,
    "friendJoinsGameSoundEnabled": true,
    "friendPlaysGameSoundEnabled": true,
    "friendPlaysGameToastEnabled": true,
    "privateMessageSoundEnabled": true,
    "privateMessageToastEnabled": true,
    "friendJoinsGameToastEnabled": true,
    "notifyOnAtMentionOnlyEnabled": false,
    "afterGameReviewEnabled": true,
    "toastPosition": "BOTTOM_RIGHT",
    "toastScreen": 0,
    "toastDisplayTime": 5000
  },
  "themeName": "default",
  "lastGameType": "faf",
  "localization": {},
  "rememberLastTab": true,
  "showPasswordProtectedGames": true,
  "showModdedGames": true,
  "ignoredNotifications": [],
  "lastGameMinRating": 800,
  "lastGameMaxRating": 1300,
  "ladder1v1": {
    "factions": [
      "aeon",
      "cybran",
      "uef",
      "seraphim"
    ]
  },
  "news": {
    "lastReadNewsUrl": "http://direct.faforever.com/2019/03/king-of-badlands-tournament-march-30th/"
  },
  "developer": {
    "gameRepositoryUrl": "https://github.com/FAForever/fa.git"
  },
  "vaultPrefs": {
    "onlineReplaySortConfig": {
      "sortProperty": "startTime",
      "sortOrder": "DESC"
    },
    "mapSortConfig": {
      "sortProperty": "statistics.plays",
      "sortOrder": "DESC"
    },
    "modVaultConfig": {
      "sortProperty": "latestVersion.createTime",
      "sortOrder": "DESC"
    }
  },
  "gameListSorting": [],
  "gameTileSortingOrder": "PLAYER_DES",
  "unitDataBaseType": "RACKOVER",
  "storedCookies": {},
  "lastGameOnlyFriends": false
}

唯一重要的部分是"forgedAlliance"

  "forgedAlliance": {
    "customMapsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Maps",
    "preferencesFile": "/home/t/.wine/drive_c/users/t/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs",
    "officialMapsDirectory": "/home/t/faf/./Maps",
    "modsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Mods",
    "port": 6112,
    "autoDownloadMaps": true,
    "executableDecorator": "\"%s\""
  },

我运行命令来获取这个:

  "forgedAlliance": {
    "path": "/home/t/.steam/steam/steamapps/common/Supreme Commander Forged Alliance",
    "installationPath": "/home/t/.steam/steam/steamapps/common/Supreme Commander Forged Alliance",
    "customMapsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Maps",
    "preferencesFile": "/home/t/.steam/steam/steamapps/compatdata/9420/pfx/drive_c/users/steamuser/Local Settings/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs",
    "officialMapsDirectory": "/home/t/faf/./Maps",
    "modsDirectory": "/home/t/My Games/Gas Powered Games/Supreme Commander Forged Alliance/Mods",
    "port": 6112,
    "autoDownloadMaps": true,
    "executableDecorator": "/home/t/faf/run \"%s\""
  },

有效的命令(在标准情况下,物体不会移动)是:

if ! grep -q '"path"' $user_path/.faforever/client.prefs > /dev/null
then
    sed -i '12i"path": "'$user_path'/.steam/steam/steamapps/common/Supreme Commander Forged Alliance",' $user_path/.faforever/client.prefs
    sed -i '13i"installationPath": "'$user_path'/.steam/steam/steamapps/common/Supreme Commander Forged Alliance",' $user_path/.faforever/client.prefs
fi
! grep -q '"preferencesFile": "'$user_path'/.steam/steam/steamapps/compatdata/9420/pfx/drive_c/users/steamuser/Local Settings/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs",' $user_path/.faforever/client.prefs > /dev/null && sed -i 's_"preferencesFile".*_"preferencesFile": "'$user_path'/.steam/steam/steamapps/compatdata/9420/pfx/drive\_c/users/steamuser/Local Settings/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs",_' $user_path/.faforever/client.prefs
! grep -q '"executableDecorator": "'$user_path'/faf/",' $user_path/.faforever/client.prefs > /dev/null && sed -i 's_"executableDecorator".*_"executableDecorator": "'$user_path'/faf/run \\"%s\\""_' $user_path/.faforever/client.prefs

答案1

jq命令将进行这些更改:

jq --arg user_path "$user_path" '
    .forgedAlliance += {
        installationPath: ($user_path + "/.steam/steam/steamapps/common/Supreme Commander Forged Alliance"),
        path: ($user_path + "/.steam/steam/steamapps/common/Supreme Commander Forged Alliance"),
        preferencesFile: ($user_path + "/.steam/steam/steamapps/compatdata/9420/pfx/drive_c/users/steamuser/Local Settings/Application Data/Gas Powered Games/Supreme Commander Forged Alliance/Game.prefs"),
        executableDecorator: ($user_path + "/faf/run \"%s\"")
    }'

这使用

  1. --arg user_path "$user_path"将shell变量带入jq程序中(您也可以使用变量绑定运算符 "'"$user_path"'" as $user_path |,但这会涉及丑陋的引号拼接)
  2. 更新分配.forgedAlliance +=来处理整个文件,仅更新“forgedAlliance”键的值合并它与右边的内容。
  3. 一个新鲜的构造的对象从{}仅包含您想要在其中计算的新键值。如果存在同名的现有密钥,它们将被替换。
  4. $user_path访问我们上面所做的变量绑定。

空格是可选的 - 它只是为了让网站更容易阅读。

jq 始终输出为有效的 JSON,因此您无需进行任何逗号清理。您可能会发现sponge来自 moreutils 的命令对于更新文件本身很有用,因为 jq 中没有-i等效项,但您也可以重定向到另一个文件

jq ... > tmpfile
mv tmpfile prefs.json

并手动绕过它。

与您的代码所做的有一个(轻微?)差异:如果“路径”出现在文件中的任何位置path,则您没有进行任何更改。installationPath无法直接使用 jq 复制该命令,但如果有必要的语义元素,您可以将命令分成两部分(一个用于路径,一个用于所有时间)。该命令将总是进行更改,但如果它已经具有相同的键值,则不会产生任何效果。


如果这是一组固定的替换,您还可以创建一个文件,其中仅包含上面第 3 点中的对象(作为真正的 JSON,不是动态计算的),然后使用

jq --slurpfile tmp rhs.json '.forgedAlliance += tmp[0]'

与上面的 big 命令效果相同。

答案2

对于字符串替换,请将字符串放在下面的占位符 A、B 中,
用 \/ 转义 A 中的任何 /,并将 ~ 作为 sed 子分隔符而不是 _
例如 A -> "executableDecorator":
(B 你新插入的字符串)

sed -E '/A/{N;/A.*\n\s*\}/ {s~(A).*\n~\1B\n~;b} ;s~(A).*\n~\1B,\n~ }' B.json

一些扩展实例;

sed -E '/"executableDecorator":/{N;/"executableDecorator":.*\n\s*\}/ {s~("executableDecorator":).*\n~\1B\n~;b} ;s~("executableDecorator":).*\n~\1B,\n~ }' B.json

相关内容