1

Given a file with the following content:

pkg1:
  replicaCount: 1
  image:
    tag: latest
    pullPolicy: Always

pkg2:
  replicaCount: 2
  image:
    tag: latest
    pullPolicy: Always

I want to search for the tag that is attached to a specific package (ie. pkg1) and replace latest with something else (ie. junk) so that I get:

pkg1:
  replicaCount: 1
  image:
    tag: junk
    pullPolicy: Always

pkg2:
  replicaCount: 2
  image:
    tag: latest
    pullPolicy: Always

Thanks to the accepted answer for this post (Find a string and replace another string after the first is found) I can do it using 'ex':

printf "%s\n" "/pkg1//latest/s//junk/" x | ex info.txt;

but the problem is that this is running in an image which doesn't have 'ex'.

Is there a way to do with with 'awk'? I suppose I can read in the file line by line and do something that way, but I was looking for something simpler. If it were single line, it wouldn't be an issue. And I can't always assume it's the first occurrence or the second occurrence. Hence I have to search for the package name and only replace the tag for that package.

1
  • 1
    Is this an YAML file? Recommend using syntax aware parsers for modifying their content – Inian Jun 11 '20 at 6:00
1

If perl is available:

PKG=pkg1 FIELD=tag VALUE=junk perl -pi -e '
  $pkg = $1 if /^(\S.*?)\s*:\s*$/;
  s/^\s+\Q$ENV{FIELD}\E\s*:\s*\K.*/$ENV{VALUE}/ if $pkg eq $ENV{PKG}' -- "$file"

Except with the recent versions of the GNU implementation, awk doesn't support editing files in-place. If your awk is actually a recent gawk, you could do the equivalent of the above with:

PKG=pkg1 FIELD=tag VALUE=junk gawk -i inplace -e '
  match($0, /^([^[:space:]](.*[^[:space:]])?)[[:space:]]*:[[:space:]]*$/, f) {
   pkg = f[1] ""
 }
 pkg == ENVIRON["PKG"] {
   $0 = gensub("^([[:space:]]+"ENVIRON["FIELD"]"[[:space:]]*:[[:space:]]*).*", \
               "\\1" ENVIRON["VALUE"], 1)
 }
 {print}' -E /dev/null "$file"

I have tried to work around a few of the limitations and awkwardnesses of the awk API:

  • parameters passed as env vars as using the -v var=value method mangles backslashes
  • pkg = f[i] "": append a "" to make sure pkg is considered as string to ensure the comparison is always a string comparison (awk doesn't have the == vs eq of perl)
  • -e 'code' -E /dev/null "$file" (a GNU extension) is required for it to work with arbitrary file names.

but there are still a few left, which means it could still not work properly in some corner cases with some input:

  • There's no equivalent of perl's \Q/\E, so it won't work properly if the FIELD contains awk regexp operators (.^${}[]\?+*| characters)
  • it won't work properly if VALUE contains & or \ characters as those are treated specially in gawk's gensub() replacement argument.
1

The simplest way would be to use a command line YAML parser:

$ yq -y '.pkg1.image.tag |= "junk"' file
pkg1:
  replicaCount: 1
  image:
    tag: junk
    pullPolicy: Always
pkg2:
  replicaCount: 2
  image:
    tag: latest
    pullPolicy: Always

I'm using yq, Andrey Kislyuk's jq wrapper for YAML documents (there are other tools called yq too, make sure you use the correct one).

The command sets the .pkg1.image.tag key's value to the string junk, and the -y option is used to generate YAML output (as opposed to the default which is JSON).

With the yq that I believe is the default on Debian-based Linux distributions, you will have to convert the YAML document to JSON, do the modification with jq, and the convert it back:

$ yq r -j file.yml | jq '.pkg1.image.tag |= "junk"' | yq r -P -
pkg1:
  replicaCount: 1
  image:
    tag: junk
    pullPolicy: Always
pkg2:
  replicaCount: 2
  image:
    tag: latest
    pullPolicy: Always

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.