【问题标题】:How to update YAML files using shell pipes?如何使用 shell 管道更新 YAML 文件?
【发布时间】:2017-07-28 16:57:52
【问题描述】:

我正在寻找一种简单的 UNIX 方法,用于将使用 shell 脚本获得的数据保存到 YAML 文件中,例如,我想使用 shell 管道将系统中已安装包(rpms、pypi)的列表保存在单个 YAML 文件中,类似:

rpm -qa |排序 | some-yaml-tool manifest.yaml system.packages

预期结果将是一个manifest.yaml 文件,如下所示:

system:
    packages:
        - xz-5.2.2-2.fc24.x86_64
        - xz-devel-5.2.2-2.fc24.x86_64

虽然我更喜欢 YAML,但我不会拒绝兼容 JSON 的解决方案。我真的很想找到一种方法来使用大多数发行版上可用的工具来做到这一点,而不是需要手动安装或从非官方 yum/deb 存储库安装的深奥。

如果工具很智能,它应该能够在文件和内部路径存在时创建它。

请注意,我必须从列表中删除 jq 工具,因为它确实是 not allow in-place editing

【问题讨论】:

  • jq <filename | sponge filename 可以很好地进行就地编辑——而tempfile=$(mktemp -t "$in.XXXXXX"); jq <"$in" >"$tempfile" && mv "$tempfile" "in" 几乎可以移植到人们可能关心的任何地方。鉴于sed -i 不能在 GNU 和 BSD 调用之间移植,因此无论如何都要使用替代公式。
  • jq 显然不是一个选项,因为在尝试在包装器中实现缺失的行为时可能会出错的事情太多。是否很容易以损坏的文件告终。性能也是另一个问题,我不想为每个处理的行创建临时文件。
  • 谁说您需要为每条处理线创建一个实例?编写 jq 代码来读取输入流并根据该流中的多个对或元素修改传递的内容是微不足道的。
  • 为每次调用创建一个临时文件是sed -i(以及几乎所有其他就地编辑实例)在幕后所做的。通过代表您的单独工具而不是您自己的代码创建临时临时文件,您并没有减少临时临时文件的数量。
  • 请记住,在底层系统调用接口级别对就地编辑的支持极为有限(就地修改的内容需要在操作之前和之后具有相同的长度,除非您'重新追加或截断),因此如果您希望 sed -i 或类似工具创建临时文件,您希望它在内存中缓冲未知数量的内容(在编辑进行期间内容不完整且一致,从而冒着您数据完整性的风险)。对于需要支持大于 RAM 的文件的工具来说,这是不可行的。

标签: json shell yaml


【解决方案1】:

将包名插入 YAML 文件

您根本不需要为您的预期用例提供就地编辑支持。注意 YAML 是 JSON 的超集,因此来自 jq 的所有输出也是有效的 YAML:

rpm -qa | sort | jq -Rn '{"system": {"packages": [inputs]}}'

会生成格式如下的输出文件:

{
  "system": {
    "packages": [
      "bar",
      "baz",
      "foo"
    ]
  }
}

...只通过一次。为了使这个 YAML 更加地道,您可以使用 PyYAML 库通过一个小型 Python shim 进行解析:

yaml_format() {
  python -c 'import sys, yaml; sys.stdout.write(yaml.dump(yaml.load(sys.stdin), default_flow_style=False))'
}
rpm -qa | sort | jq -Rn '{"system": {"packages": [inputs]}}' | yaml_format

...将生成表单的内容:

system:
  packages:
  - bar
  - baz
  - foo

就地更新 JSON 文件

假设您已经有一个 JSON 文件,其中的内容 您想要保留的清单。在这种情况下,明确提出的问题变得相关。一个安全的(尽管仅限 GNU)实现如下所示:

atomic_update() {
  # usage: atomic_update filename command arg1 arg2 ...
  local filename tempfile retval=0
  filename=$1; shift || return
  tempfile=$(mktemp -t -- "$filename.XXXXXX") || return
  if "$@" <"$filename" >"$tempfile"; then
    # Make a best-effort attempt to preserve permissions
    chown --reference="$filename" -- "$tempfile" &>/dev/null ||:
    chmod --reference="$filename" -- "$tempfile" &>/dev/null ||:
    mv -- "$tempfile" "$filename"
  else
    retval=$?
    rm -f -- "$tempfile"
  fi
  return "$retval"
}

rpm -qa | sort | atomic_update manifest.yml jq -Rn '.system.packages = [inputs]'

【讨论】:

  • 似乎atomic_update部分在Mac OS上不起作用,还在尝试找出原因。
  • 抛出一些错误,因为我把重定向到安静的 stderr(当你没有带有 GNU 扩展的 chown 和 chmod 时)在错误的地方,但它似乎在 10.12.6 上工作,即使原始代码。 printf '%s\n' 2 3 1 &gt;test.txt; atomic_update test.txt sort 离开 test.txt 即使在 OS X 上也排序。(注意,如果没有 GNU chmod,新的 test.txt 可以拥有与旧的不同的文件权限)。
【解决方案2】:

虽然我还没有完全可行的解决方案,但我认为我非常接近通过json npm 包找到一个。

echo "{}" > e.json
seq 3 | xargs -0 -I XXX json -I -f e.json -e 'this.numbers=XXX'
cat e.json

现在唯一缺少的部分是关于如何使其使用列表并合并到它而不是覆盖值。

【讨论】:

  • seq 3 不使用 NUL 分隔符,那么 -0xargs 参数是什么?如果这个没有-0调用,它会调用json每个序列号一次。鉴于您对问题中的性能表示担忧,这让我感到震惊,因为这种方法通常不可行(特别是当不是用于三个整数的序列,而是用于具有数百个包名称的序列时)。
  • sea 3 仅用于快速测试。我一拿到电脑就去测试它,看起来做对了。
猜你喜欢
  • 2016-09-26
  • 2015-04-17
  • 2020-03-21
  • 1970-01-01
  • 2020-11-20
  • 1970-01-01
  • 2021-03-15
  • 2021-03-20
  • 2020-01-30
相关资源
最近更新 更多