1

I have a pom.xml file and I want to replace the version inside any of the dependency nodes from 0.1-SNAPSHOT to a different version (let's say NEW-VERSION), the only restriction I have is that the replacement should be done only if the groupId matches a particular text, lets say: com.company.xyz.

That being said, let's say this is the dependencies section of my pom.xml:

<dependencies>
        <dependency>
            <groupId>com.company.xyz</groupId>
            <artifactId>xyz-xx-utils</artifactId>
            <version>0.1-SNAPSHOT</version>
        </dependency>

        <dependency>
            <groupId>com.ibm.xyz</groupId>
            <artifactId>xyz</artifactId>
            <version>56.1</version>
        </dependency>
</dependencies>

After applying the sed command I want it to look like:

<dependencies>
        <dependency>
            <groupId>com.company.xyz</groupId>
            <artifactId>xyz-xx-utils</artifactId>
            <version>NEW-VERSION</version>
        </dependency>

        <dependency>
            <groupId>com.ibm.xyz</groupId>
            <artifactId>xyz</artifactId>
            <version>56.1</version>
        </dependency>
</dependencies>

Here is what I have tried without any success:

newVersion="NEW-VERSION"

sed -i "s/\(<dependency>\)\(<groupId>com.company.xyz</groupId>\)\(<artifactId>.*\</artifactId>\)\(<version>0.1-SNAPSHOT</version>\)\(</dependency>\)/\1\2\3$newVersion\5/" pom.xml

I was wondering if the reason why this does not work is because my text has multiple lines, so I researched a little bit but I did not understand the syntax used to work with multiple lines (some N thing).

Btw, I am including this piece of code in a bash file that performs other operations, that is why I want to do this using sed or any other "tool" that is bash compatible.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
Annie
  • 145
  • 4
  • 12

4 Answers4

2

This is a sed solution – a little more unwieldy than awk, but not horribly so. If you store this in a file, say, sedscr:

/<dependency>/ {
    :start
    N
    /<\/dependency>$/!b start
    /<groupId>com.company.xyz<\/groupId>/ {
        s/\(<version>\)0\.1-SNAPSHOT\(<\/version>\)/\1NEW-VERSION\2/
    }
}

You can run it with sed -i sedscr pom.xml. This is how it works:

/<dependency>/ {   # If the line matches "<dependency>"
    :start         # Label to branch to
    N              # Append next line to pattern space

    # If the pattern space does not end with "</dependency>", branch to start
    /<\/dependency>$/!b start

    # We now have the complete <dependency> element in the pattern space

    # If the groupId element matches "com.company.xyz"
    /<groupId>com.company.xyz<\/groupId>/ {

        # Substitute the version for "NEW-VERSION"
        s/\(<version>\)0\.1-SNAPSHOT\(<\/version>\)/\1NEW-VERSION\2/
    }
}

Your sed didn't work because you try to match across newlines, but sed only has one line at a time in its pattern space – unless you start using commands like N ("Next"). Even when you do have multiple lines in pattern space, you have to account for newlines and match them with \n (and take care of whitespace and so on).

You could use this in a Bash script as follows:

#!/bin/bash

new_version='NEW-VERSION'

sed '/<dependency>/ {
    :start
    N
    /<\/dependency>$/!b start
    /<groupId>com.company.xyz<\/groupId>/ {
        s/\(<version>\)0\.1-SNAPSHOT\(<\/version>\)/\1'"$new_version"'\2/
    }
}' pom.xml

Quoting like this avoids potential trouble when the sed commands are not within single quotes; only $new_version is in double quotes to allow for interpolation.

Benjamin W.
  • 46,058
  • 19
  • 106
  • 116
2

I would recommend using maven properties which are clear and accidentally easy to process with sed search and replace.

So pom.xml will look like:

<properties>
    <xyz-xx-utils.version>2.0</xyz-xx-utils.version>
</properties>
<dependencies>
    <dependency>
        <groupId>com.company.xyz</groupId>
        <artifactId>xyz-xx-utils</artifactId>
        <version>${xyz-xx-utils.version}</version>
    </dependency>
</dependencies>

Command line to change the version will be:

sed -i.bak  "s/<xyz-xx-utils.version>.*<\/xyz-xx-utils.version>/<xyz-xx-utils.version>NEW-VERSION<\/xyz-xx-utils.version>/g"  $pomfile
Grzegorz Kazior
  • 371
  • 8
  • 12
1

gnu-awk with custom RS (record separator) suites this better:

awk -v RS="</dependency>" 'index($0, "<groupId>com.company.xyz</groupId>"){
    sub(/0\.1-SNAPSHOT/, "NEW-VERSION")} RT{$0 = $0 RT} 1' file

Awk command breakup

-v RS="</dependency>"               # sets input record separator as </dependency>
index($0, "<groupId>...")           # for lines that have give string
sub(/0\.1-SNAPSHOT/, "NEW-VERSION") # replace 0.1-SNAPSHOT by NEW-VERSION
1                                   # default action to print the line

In short this awk command breaks input data into records as section containing multiple lines before </dependency> and then checks for presence of desired groupId using index function and replaces the text using sub function.

Output:

<dependencies>
        <dependency>
            <groupId>com.company.xyz</groupId>
            <artifactId>xyz-xx-utils</artifactId>
            <version>NEW-VERSION</version>
        </dependency>


        <dependency>
            <groupId>com.ibm.xyz</groupId>
            <artifactId>xyz</artifactId>
            <version>56.1</version>
        </dependency>

</dependencies>
anubhava
  • 761,203
  • 64
  • 569
  • 643
  • Thank you very much @anubhava! Would you please explain the code a little bit? I also noticed that the output in the console is perfect but the actual file is not changing, do you know why? – Annie Jan 23 '16 at 00:06
  • 1
    The idea is to redirect sed's output to a file. By default, it prints to stdout, as unix tools tend to do. try using `sed -i`. – Trent Bartlem Jan 23 '16 at 00:09
  • Thanks for your suggestion @TrentBartlem, I am using the code that anubhava provided (which uses awk instead of sed) I changed it a little bit to send the output to a file like this: awk -v RS="" 'index($0, "com.company.xyz"){ sub(/0\.1-SNAPSHOT/, "NEW-VERSION")} RT{$0 = $0 RT} 1' file > otherFile.txt and it worked ok, but if I change it to send the output to the same file that I used as input then the file ends up empty. Do you know how can I accomplish that? – Annie Jan 23 '16 at 00:30
  • For awk, try http://stackoverflow.com/questions/16529716/awk-save-modifications-inplace – Trent Bartlem Jan 23 '16 at 01:00
  • 1
    @Annie Redirection takes place before other commmands, so if you do `cat file > file`, the file is truncated first, and then there is nothing left to work with - you end up with an empty file. Unless there is a builtin solution like `sed -i`, you have to do something like `cat file > tempfile && mv tempfile file`. – Benjamin W. Jan 23 '16 at 02:43
  • 1
    @Annie: To save changes back to original file use: `awk -v RS="" 'index($0, "com.company.xyz"){sub(/0\.1-SNAPSHOT/, "NEW-VERSION")} RT{$0 = $0 RT} 1' file > file.tmp && mv file.tmp file` – anubhava Jan 23 '16 at 07:51
0

The following awk programs, which have been tested with gawk and mawk, simplify and generalize the awk program using RS given elsewhere on this page. By using printf, they also ensure spacing is preserved.

(a) simplification

awk -v RS="</dependency>" '/<groupId>com.company.xyz<\/groupId>/ {
  sub(/0\.1-SNAPSHOT/, "NEW-VERSION")} {printf "%s", $0 RS}'

(b) simplification with generalization

awk -v groupId="com.company.xyz" -v RS="</dependency>" '
  $0 ~ "<groupId>" groupId "</groupId>" {
    sub("<version>.*</version>", "<version>NEW-VERSION</version>")
  } 
  {printf "%s", $0 RS}'
peak
  • 105,803
  • 17
  • 152
  • 177