2

I've multiple configurations in YAML file and I need to change some paramaters using a Bash script. Is it possible? I want to avoid using any external dependency.

My YAML Looks like

%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"

I want it to behave like if I provide --mic logitec --speaker hk34 then it should modify alsa_device_name for both mic and speaker in my config to

%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"

Is it possible to do it using only Bash, and if so, how? For now I am using a Python script but this adds an extra dependency of having python3, which is something I want to avoid.

6
  • 1
    Sure, try awk or sed.
    – steve
    Jul 30 at 9:57
  • 2
    Which system are you on GNU based? or BSD? And parsing with standard text processing tools is a bad idea. Also your header line %YAML 1.2 makes your input incompatible to YAML grammar
    – Inian
    Jul 30 at 10:17
  • @Inian I'm using GNU based system. Jul 30 at 10:31
  • @steve I was thinking of using sed but I'm not sure how to establish a relationship between mic and alsa_device_name. Can you give me some hint Jul 30 at 10:33
  • 3
    So you don't need this to be done in bash? sed is OK? Can we use any other standard utilities like perl or awk all of which will be installed on a Linux system?
    – terdon
    Jul 30 at 10:53
5

Firstly, parsing a language like YAML that has a well defined grammar using line oriented tools is a bad idea. Don't use it in production! Never.

There are syntax aware tools for parsing YAML via command line like Python yq and Go yq. They support constructs within YAML like anchors, block literals which is not understood by standard line oriented tools.

For one time processing like above, you could use awk like

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 }

You could put the above in a awk script (.awk) and run it as awk -f script.awk yaml or run it as part of command line. You can also define the values in the command line as

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

and process the arguments in the BEGIN block as

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

Also note that your input isn't a valid YAML file, the header line %YAML 1.2 would make most standard YAML parsers throw an error on the syntax of your input.

4

I was waiting until you accepted another answer to write this, as it adds a dependency on 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

Most of the code in the script is command line parsing and logic related to the given command line options. This was due to a requirement, or at least a suggestion, in the question to be able to use the solution with a set of convenient options on the command line. The actual code that does something is in the yaml_update function and consists of three lines of code (the yq command, and it's only three lines because it would be too long on a single line).

This script would be used like so:

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

It takes two optional command line options, -m and -s (long options, suggested in the question, are not standard and not supported by the getopts built-in in bash) to set the mic and/or the speaker Alsa device name respectively.

The YAML document is read on standard input and the resulting document is written to standard output.

The update is done with yq using an expression that selects the correct top-level object based on the name key, and then the correct array element from the components array using its name key. It then updates the alsa_device_name value under the selected elements' parameters key.

For convenience, the yq call was moved into a shell function (we're potentially doing two extremely similar calls to yq, so this seemed reasonable).

The output, if changing the given document like shown above, would be

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"

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.