0

I have a Jenkinsfile which analyses some local artifactory server for new versions of the parent and dependencies. When I confirm that there is a new version, I want to replace the old version with a new one.

so given a pom.xml containing this dependency:

<dependency>
  <groupId>abcde</groupId>
  <artifactId>xyz</artifactId>
  <version>1.2.4</version>
</dependency>

... and assume that there is a newer version 1.3.5 of abcde.xyz ...

I would like to update that with the following sed command:

sed -i '/<dependency>/,/<\/dependency>/{
    /<groupId>abcde<\/groupId>/{
        /<artifactId>xyz<\/artifactId>/{
            s/<version>1.2.4<\/version>/<version>1.3.5<\/version>/
        }
    }
}' pom.xml

If I create a pom.xml either containing the above xml or only the above xml and execute this statement, the file is modified, but nothing changes. removing the -i flag basically dumps the file 1:1 to stdout - what am I missing?

expected output:

<dependency>
  <groupId>abcde</groupId>
  <artifactId>xyz</artifactId>
  <version>1.3.5</version>
</dependency>

(In the jenkinsfile it is parameterized and escaped like below, but since the basic statement is not working, please focus on that.)

sh """
sed -i '/<dependency>/,/<\\/dependency>/{
    /<groupId>${groupId}<\\/groupId>/{
        /<artifactId>${artifactId}<\\/artifactId>/{
            s/<version>${version}<\\/version>/<version>${targetVersion}<\\/version>/
        }
    }
}' pom.xml
"""
3
  • 1
    Why would you use sed for this? There are plenty of XML parsers for shell Commented Apr 24 at 13:11
  • @0stone0 built in? - I'm running in a jenkins kubernetes container and cannot install extra tools Commented Apr 24 at 13:13
  • 2
    That image probably contains python or perl, both a better option imho. Commented Apr 24 at 13:17

4 Answers 4

1

Directly in a shell if you do sed 's/${groupId}/foo/' the groupId variable won't be expanded as it's inside single quotes. That might also occur when you wrap it in sh """ ... """ as you're doing for whatever you're running it within ("jenkinsfile"?), I don't know.

/<dependency>/,/<\/dependency>/{ foo } means "for each of the lines from /<dependency>/ to /<\/dependency>/ do foo. So you're isolating the block of lines you're interested in but then foo is still applied 1 line at a time within that block. You're then doing /<groupId>abcde<\/groupId>/ which matches that line and then trying to do /<artifactId>xyz<\/artifactId>/ within that line, but <artifactId> doesn't exist on the same line as <groupId> and so will never do anything.

You can do this easily, more robustly, and without duplicating any of the block start/end expressions with awk, e.g. using a small state machine with GNU awk for -i inplace (which I assume you have since you're using GNU sed):

$ awk -i inplace -v grp='abcde' -v art='xyz' -v oldVer='1.2.4' -v newVer='1.3.5' '
    $1 == "</dependency>"                                   { s=0 }
    $1 == "<dependency>"                                    { s=1 }
    (s==1) && index($0, "<groupId>" grp "</groupId>")       { s=2 }
    (s==2) && index($0, "<artifactId>" art "</artifactId>") { s=3 }
    (s==3) && index($0, "<version>" oldVer "</version>") {
        $0 = "<version>" newVer "</version>"
    }
    { print }
' pom.xml

$ cat pom.xml
<dependency>
  <groupId>abcde</groupId>
  <artifactId>xyz</artifactId>
<version>1.3.5</version>
</dependency>

In addition to doing what you asked for that solves the problem you would have had with sed being unable to do literal string comparisons and so requiring you to escape all regexp metachars, sed delimiters, and backreferences, see Is it possible to escape regex metacharacters reliably with sed.

Sign up to request clarification or add additional context in comments.

2 Comments

works perfectly when I run it in a simple pipeline, but when I run it in my main Jenkinsfile it fails with java.io.IOException: cannot find current thread it also seems to ignore the 's in the variable line. It does not appear to have an effect. I will update the question
@JoSSte it'd be better if you asked a new question since you already got answers to the question you asked. Your new question about how to call a script from "Jenkinsfile" sounds like it'll be unrelated to this one about how to modify a line within hierarchical delimited blocks of text.
1

Updating sed addresses to include the remaining lines until </dependency> does the trick

sed -i '/<dependency>/,/<\/dependency>/{
    /<groupId>abcde<\/groupId>/,/<\/dependency>/{
        /<artifactId>xyz<\/artifactId>/,/<\/dependency>/{
            s/<version>1\.2\.4<\/version>/<version>1.3.5<\/version>/
        }
    }
}' pom.xml

Dollar sign can also be used on inner addresses to indicate the end of the active space and / separator on s command can be substituted by another character to reduce escaping

sed -i '/<dependency>/,/<\/dependency>/{
    /<groupId>abcde<\/groupId>/,$ {
        /<artifactId>xyz<\/artifactId>/,$ {
            s@<version>1\.2\.4</version>@<version>1.3.5</version>@
        }
    }
}' pom.xml

Comments

1

If you can, I recommend using yq or similar instead of sed.

# Hard-coded new version.
yq '.dependency.version = "1.3.5"' pom.xml
# Passing in the new version as an "argument".
V=1.3.5 yq '.dependency.version = strenv(V)' pom.xml

Input:

<dependency>
  <groupId>abcde</groupId>
  <artifactId>xyz</artifactId>
  <version>1.2.4</version>
</dependency>

Output:

<dependency>
  <groupId>abcde</groupId>
  <artifactId>xyz</artifactId>
  <version>1.3.5</version>
</dependency>

Use yq -i to modify the file in-place instead of sending the output to STDOUT.

Comments

0

You can use Raku/Sparrow for that. Given that data.xml contains XML code:

task.bash

cat data.xml

task.check*

# find line with version for artifact xyz
between: { "<dependency>" } { "</dependency>" }
<artifactId>xyz</artifactId>
regexp: "<version>" \s* \S+ \s* "</version>"
end:


code: <<HERE
!raku
my $line = streams().values.head.tail;
replace("data.xml",$line<index>,"<version>1.3.5</version>");
HERE

Test output

$ s6 --task-run .
22:58:41 :: [sparrowtask] - run sparrow task .
22:58:41 :: [sparrowtask] - run [.], thing: .
[task run: task.bash - .]
[task stdout]
22:58:41 :: <dependency>
22:58:41 ::   <groupId>abcde</groupId>
22:58:41 ::   <artifactId>xyz</artifactId>
22:58:41 ::   <version>1.2.4</version>
22:58:41 :: </dependency>
[task check]
stdout match (r) <<artifactId>xyz</artifactId>> True
stdout match (r) <"<version>" \s* \S+ \s* "</version>"> True

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.