在bash中修改yaml文件,无需任何外部依赖

在bash中修改yaml文件,无需任何外部依赖

我在 YAML 文件中有多个配置,我需要使用 Bash 脚本更改一些参数。是否可以?我想避免使用任何外部依赖项。

我的 YAML 看起来像

%YAML 1.2
---
name: mic
components:
- name: Mic
  parameters:
    period_count: 4
    alsa_device_name: "pulse"

---
name: speaker
components:
- name: Speaker
  parameters:
    period_duration_ms: 20
    period_count: 4
    alsa_device_name: "pulse"

我希望它的行为就像我提供的那样--mic logitec --speaker hk34,那么它应该在我的配置中修改alsa_device_namemicspeaker

%YAML 1.2
---
name: mic
components:
- name: Mic
  parameters:
    period_count: 4
    alsa_device_name: "logitec"

---
name: speaker
components:
- name: Speaker
  parameters:
    period_duration_ms: 20
    period_count: 4
    alsa_device_name: "hk34"

是否可以仅使用 Bash 来完成此操作?如果可以,如何实现?现在我使用的是Python脚本,但这增加了对having的额外依赖python3,这是我想避免的。

答案1

首先,使用面向行的工具解析像 YAML 这样具有明确定义语法的语言是一个坏主意。不要在生产中使用它!绝不。

有一些语法感知工具可以通过命令行解析 YAML,例如Pythonyqyq。它们支持 YAML 中的结构,例如锚点、块文字,而标准面向行的工具无法理解这些结构。

对于像上面这样的一次性处理,您可以使用awk

BEGIN {
    map["name: mic"] = "\"logitec\""
    map["name: speaker"] = "\"hk34\""
}

match($0, "name: mic") || match($0, "name: speaker") {
    key = substr($0, RSTART, RLENGTH)
}

/alsa_device_name/ {
    sub(/:.*/, ": " map[key])
}

{ print }

您可以将上述内容放入awk脚本 ( .awk) 中并作为命令行运行awk -f script.awk yaml或作为命令行的一部分运行。您还可以在命令行中将值定义为

awk -v mic='"logitec"' -v speaker='"hk34"' -f script.awk yaml

并将块中的参数处理BEGIN

BEGIN {
    map["name: mic"] = mic
    map["name: speaker"] = speaker
}

另请注意,您的输入不是有效的 YAML 文件,标题行%YAML 1.2将使大多数标准 YAML 解析器在您的输入语法上引发错误。

答案2

yq我一直等到你接受另一个答案才写这个,因为它增加了对from 的依赖https://kislyuk.github.io/yq/

#!/bin/bash

unset mic
unset speaker

while getopts m:s: opt; do
        case $opt in
                m)
                        mic=$OPTARG
                        ;;
                s)
                        speaker=$OPTARG
                        ;;
                *)
                        echo 'invalid option' >&2
                        exit 1
        esac
done

shift "$(( OPTIND - 1 ))"

yaml_update () {
        key=${1,,}    # lower-case, e.g. "mic"
        Key=${key^?}  # title-case, e.g. "Mic"
        value=$2

        yq -Y --arg key "$key" --arg Key "$Key" --arg value "$value" '
                (select(.name == $key).components[] |
                select(.name == $Key).parameters.alsa_device_name) |= $value'
}

if [ "${mic+set}" = "set" ]; then
        yaml_update mic "$mic"
else
        cat -
fi |
if [ "${speaker+set}" = "set" ]; then
        yaml_update speaker "$speaker"
else
        cat -
fi

脚本中的大部分代码是命令行解析以及与给定命令行选项相关的逻辑。这是由于问题中的一个要求,或者至少是一个建议,能够在命令行上使用带有一组方便选项的解决方案。实际的代码是做某事位于yaml_update函数中,由三行代码组成(yq命令,它只有三行,因为单行代码太长)。

该脚本的使用方式如下:

./script -m logitec -s hk34 <file.yaml >file-new.yaml

它需要两个可选的命令行选项,-m并且-s(问题中建议的长选项不是标准的,并且getopts内置的不支持bash)来设置麦克风和/或扬声器分别为Alsa设备名称。

YAML 文档在标准输入上读取,并将生成的文档写入标准输出。

更新是通过yq使用一个表达式来完成的,该表达式根据键选择正确的顶级对象name,然后components使用从数组中选择正确的数组元素它是 name钥匙。然后它更新alsa_device_name所选元素parameters键下的值。

为了方便起见,该yq调用被移至 shell 函数中(我们可能会对 进行两次极其相似的调用yq,因此这似乎是合理的)。

如果像上面所示更改给定文档,则输出将是

name: mic
components:
  - name: Mic
    parameters:
      period_count: 4
      alsa_device_name: "logitec"
---
name: speaker
components:
  - name: Speaker
    parameters:
      period_duration_ms: 20
      period_count: 4
      alsa_device_name: "hk34"

相关内容