Replace value in "key: value" statement, but only on first occurence of the key in the file

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.


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 

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/