6

I have a yml file

spring:
  datasource:
    url: url
    username:test
    password: testpwd
api:
  security:
    username:foo
    password: foopwd

I want to update only the first occurrence or username and password using the command line on a Linux machine so that it looks like this:

spring:
  datasource:
    url: url
    username:toto
    password: totopsw
api:
  security:
    username:foo
    password: foopwd

when i try :

sed -i -e 's!^\(\s*username:\)[^"]*!\1toto!' test.yml

he change all usernames

Jeff Schaller
  • 66,199
  • 35
  • 114
  • 250
siraj
  • 185
  • 1
  • 8
  • Since the file is specifically a YAML file, would `yq` (a wrapper around `jq`) be acceptable, or are you purely looking for text processing solutions? (A `yq`-based solution would work whether `spring` or `api` was first; conversely, a text processing solution would work regardless of the section name.) – user7761803 Feb 27 '21 at 22:12

5 Answers5

6

Another sed alternative:

sed '1,/username/{/username/ s/:.*/:toto/};
     1,/password/{/password/ s/:.*/:totopsw/}' infile

1,/regex/ start from the first line up-to first line that matched with given regex (here it's username string), change the "username"; doing the same for "password" part.

αғsнιη
  • 40,939
  • 15
  • 71
  • 114
4

If your file is small enough to fit into memory, you can try reading the entire thing as a single record. That way, sed will only make the substitution for the first time it sees the pattern on the "line" (record):

$ sed -Ez 's/(username:)[^\n]*/\1toto/; s/(password:)[^\n]*/\1totopsw/' file.yaml 
spring:
  datasource:
    url: url
    username:toto
    password:totopsw
api:
  security:
    username:foo
    password: foopwd

To make the change in the original file, just add -i:

sed -i -Ez 's/(username:)[^\n]*/\1toto/; s/(password:)[^\n]*/\1totopsw/' file.yaml 
terdon
  • 234,489
  • 66
  • 447
  • 667
4

If the file is longer, here is an awk-based solution that processes it line-wise:

awk '/^[[:space:]]+username/ && !u_chng{sub(/:.+$/,": toto"); u_chng=1}
     /^[[:space:]]+password/ && !p_chng{sub(/:.+$/,": totospw"); p_chng=1} 1' input.yml 

This will check each line whether it starts with username or password, respectively. If so, and the associated flags u_chng and p_chng are not yet set, it sets the value after the : to your new desired one and sets the respective flag so that any further occurrences of these keywords are disregarded.

Result:

spring:
  datasource:
    url: url
    username: toto
    password: totospw
api:
  security:
    username:foo
    password: foopwd

Note that if you use an awk implementation that doesn't understand character classes ([[:space:]]), change

/^[[:space:]]+username/

to

/^[ \t]+username/
terdon
  • 234,489
  • 66
  • 447
  • 667
AdminBee
  • 21,637
  • 21
  • 47
  • 71
  • Are there `awk` implementations that don't support `[[:space:]]`? Those are really old and part of [POSIX BREs](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html), I would be very curious to know if you have come across awk flavors that don't support them. – terdon Feb 26 '21 at 16:39
  • @terdon, ThomasDickey [added classes support to `mawk` over 10 years ago](https://invisible-island.net/mawk/CHANGES.html#t20090727), but Debian has not switched to that one for `mawk` until very recently. On Ubuntu 18.04, mawk is the default awk and doesn't support POSIX character classes. – Stéphane Chazelas Feb 27 '21 at 09:14
  • @StéphaneChazelas huh. I had assumed they would have been part of its internal regex engine from day one. Thanks! – terdon Feb 27 '21 at 14:36
1

With gnu awk

awk -F '^\\s+|:' '
  BEGIN {
    a["username"] = "toto"
    a["password"] = "totopsw"
  }
  ($2 in a) {
    sub(/:.*/, ": " a[$2])
    delete a[$2]
  };1
' file
  • set fields separator to be colon or a run of whitespace
  • initilaize array a with keywords to search for n their corresponding replacemens.
  • after every successful search-n-replace operation , delte the key.
guest_7
  • 5,698
  • 1
  • 6
  • 13
1

if a bash script is valid, you coud use the sed quit command:

#!/bin/bash

sed -E '
    # If we find an username
    /username/ {

        # make the sobstitution of the username
        s/^([[:space:]]*username:)/\1toto/g

        n # Take the next line with the password

        # Sobstitute the password
        s/^([[:space:]]*password:).*$/\1totopw/g

        q1 # Quit after the first match
    }
' test.yml > new_test.yml

# How many line we have taken
len_line=$(sed -n '$=' new_test.yml)

# write the other line
sed "1,${len_line}d" test.yml >> new_test.yml

# rename all the file
rm -f test.yml
mv new_test.yml test.yml
DanieleGrassini
  • 2,769
  • 5
  • 17