使用 find 和 jq 进行就地编辑会产生不一致的结果

使用 find 和 jq 进行就地编辑会产生不一致的结果

我正在尝试使用 jq 修改相当大量的文件如果该文件包含特定值的属性:

find . \
    -name '*.configuration.json' \
    -type f -exec bash -c 'jq "select(.version == \"2.0\") | .identifier = \"\"" $0 | sponge "$0"' {} \;

所以我在这里学到了很多东西,比如如何实际重定向输出-exec以及如何不是使用重定向输出jq

上面的脚本适用于找到的某些文件,但对于其他文件,它只是覆盖整个文件,什么也不做。我猜这是由竞争条件引起的,但我不知道如何解决这个问题。

任何帮助或指出我可能遇到的其他陷阱将不胜感激。

答案1

您的表达式的问题jq在于,如果verisonJSON 对象的键不是2.0,则不会选择该对象并且不会输出该对象。这意味着您实际上删除了任何不具有2.0as 的内容version

相反,更新identifier每个对象的值,其version2.0

jq 'select(.version == "2.0").identifier = ""'

关键是更新值而不从其对象中提取它。

人们还可以使用类似以下的内容,这更像是您代码中的内容:

jq 'select(.version == "2.0") |= (.identifier = "")'

这使用更新运算|=符 来更新 所选择的对象select()

find

find . -name '*.configuration.json' -type f -exec sh -c '
    tmpfile=$(mktemp)
    for pathname do
        cp -- "$pathname" "$tmpfile" &&
        jq "select(.version == \"2.0\").identifier = \"\"" <"$tmpfile" >"$pathname"
    done
    rm -f "$tmpfile"' sh {} +

这也避免了不必要地多次调用内联脚本,并且sh -c脚本中的循环确保保留原始文件的权限和所有权。

或者,如果您想使用sponge(我自己从未使用过,所以请将此视为未经测试的示例):

find . -name '*.configuration.json' -type f -exec sh -c '
    for pathname do
        jq "select(.version == \"2.0\").identifier = \"\"" "$pathname" |
        sponge "$pathname"
    done' sh {} +

相关内容