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

tat*_*tsu 3 sed shell-script json

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

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

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

通常这不会成为问题。

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

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

例子 :

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

sed -i 's_"executableDecorator".*_"executableDecorator": "'$user_path'/faf/run \\"%s\\"",_' $user_path/.faforever/client.prefs
Run Code Online (Sandbox Code Playgroud)

如果该行在最后:

sed -i 's_"executableDecorator".*_"executableDecorator": "'$user_path'/faf/run \\"%s\\""_' $user_path/.faforever/client.prefs
Run Code Online (Sandbox Code Playgroud)

奏效,但是!...

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

这是完全随机的。

有时一条线可能在中间有时在最后。应用程序本身(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
}
Run Code Online (Sandbox Code Playgroud)

唯一重要的部分是"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\""
  },
Run Code Online (Sandbox Code Playgroud)

我运行命令来获取这个:

  "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\""
  },
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

Mic*_*mer 6

这个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\"")
    }'
Run Code Online (Sandbox Code Playgroud)

这使用

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

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

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

jq ... > tmpfile
mv tmpfile prefs.json
Run Code Online (Sandbox Code Playgroud)

并手动绕过它。

您的代码所做的事情有一个(轻微的?)不同之处:您没有对文件中的任何位置出现“路径”path以及installationPath是否进行任何更改。没有办法直接用 jq 复制它,但是如果有必要的语义元素,您可以将命令分成两部分(一个用于路径,一个用于所有时间)。此命令将始终进行更改,但如果它已经为没有任何效果的键获得了相同的值。


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

jq --slurpfile tmp rhs.json '.forgedAlliance += tmp[0]'
Run Code Online (Sandbox Code Playgroud)

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