Edit YAML file with Bash

Since you don't want to install yq you could use python that you most probably already have installed.

Here are the fundamentals:

#!/usr/bin/python

import yaml

with open("config.yml") as f:
    y = yaml.safe_load(f)
    y['db']['admin']['password'] = 'new_admin_pass'
    print(yaml.dump(y, default_flow_style=False, sort_keys=False))

Output:

db:
  host: x.x.x.x.x
  main:
    password: password_main
  admin:
    password: new_admin_pass

A similar piece of python code as a one-liner that you can put in a bash script would look something like this (and produce the same output):

python -c 'import yaml;f=open("config.yml");y=yaml.safe_load(f);y["db"]["admin"]["password"] = "new_admin_pass"; print(yaml.dump(y, default_flow_style=False, sort_keys=False))'

If you'd like to save the output to a file, you can provide an output stream as the second argument to dump():

#!/usr/bin/python

import yaml

with open("config.yml") as istream:
    ymldoc = yaml.safe_load(istream)
    ymldoc['db']['admin']['password'] = 'new_admin_pass'

with open("modified.yml", "w") as ostream:
    yaml.dump(ymldoc, ostream, default_flow_style=False, sort_keys=False)

If you'd like to overwrite the original file, I recommend writing to a temporary file first and only if that succeeds, use os.rename to move that file in place of the original one. That's to minimize the risk of creating a corrupt config.yml in case of problems.


Note: Using a YAML parser like yq (or yq) will be a way more reliable solution.


However, I've used the following 'technique' to alter a 'pre-defined' line though the help of grep and sed like so;

/tmp/config.yml

db:
  host: 'x.x.x.x.x'
  main:
    password: 'password_main'
  admin:
    password: 'password_admin'
  1. Get the line number where your 'old-password' is located:
    grep -n 'password_admin' /tmp/config.yml | cut -d ':' -f1
    

    6

  2. Then, use sed to override that line with your new password:
    sed -i '6s/.*/    password: \'new_admin_pass\'/' /tmp/config.yml
    

The new file now looks like this:

db:
  host: 'x.x.x.x.x'
  main:
    password: 'password_main'
  admin:
    password: 'new_admin_pass'

Note

  • Keep in mind that any special chars (&, \, /) in the password will cause sed to misbehave!

  • This could fail if the indent changes, since YAML cares about indentation. Just like I mentioned above, using a YAML parser will be a much more reliable solution!


$ awk -v new="'sumthin'" 'prev=="main:"{sub(/\047.*/,""); $0=$0 new} {prev=$1} 1' file
db:
  host: 'x.x.x.x.x'
  main:
    password: 'sumthin'
  admin:
    password: 'password_admin'

or if your new text can contain escape sequences that you don't want expanded (e.g. \t or \n), as seems likely when setting a password, then:

new="'sumthin'" awk 'prev=="main:"{sub(/\047.*/,""); $0=$0 ENVIRON["new"]} {prev=$1} 1' file

See How do I use shell variables in an awk script? for why/how I use ENVIRON[] to access a shell variable rather than setting an awk variable in that second script.