1

I have a file where I need to add set of lines after some pattern. The problem is I am not able to handle the pattern which has multiple lines.

Pattern

<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>

Lines to be added after this pattern

<org.apache.jmeter.config.RestServerNodeElement guiclass="TestBeanGUI"
  testclass="org.apache.jmeter.config.RestServerNodeElement"
  testname="ATC_Test_Lab" enabled="true">
<stringProp name="TestPlan.comments"> </stringProp>
<stringProp name="authbasic">${Basic}</stringProp>
<stringProp name="authpassword">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>
<stringProp name="authuser">${User}</stringProp>
<stringProp name="extratag"></stringProp>

I tried sed command with option -r as below but it didn’t worked

sed -i.bak '/^<stringProp
name="TestPlan.user_define_classpath"></stringProp>/{N;/\n</TestPlan>/{N;/\n<hashTree>/r
Config.jmx' $file

file Config.jmx has the set of lines which needs to be added after this pattern.

Error:-

sed: -e expression #1, char 116: unterminated `s' command
0

Let's avoid boring approaches like reading the whole line as a string in some scripting language and doing a regex substitution with embedded newlines.

State machine approach in Awk. Each line is printed. If the three required lines appear consecutively, the state variable increments to 2 and the required block is printed. For any other input, state lapses to zero.

1
1 { print state }
state == 0 && /^<stringProp name="TestPlan.user_define_classpath"><\/stringProp>$/ {
  state++; next
}
state == 1 && /^<\/TestPlan>$/ {
  state++; next
}
state == 2 && /^<hashTree>$/ {
  print \
  "<org.apache.jmeter.config.RestServerNodeElement guiclass=\"TestBeanGUI\"\n" \
  "testclass=\"org.apache.jmeter.config.RestServerNodeElement\"\n" \
  "testname=\"ATC_Test_Lab\" enabled=\"true\">\n" \
  "<stringProp name=\"TestPlan.comments\"> </stringProp>\n" \
  "<stringProp name=\"authbasic\">${Basic}</stringProp>\n" \
  "<stringProp name=\"authpassword\">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>\n" \
  "<stringProp name=\"authuser\">${User}</stringProp>\n" \
  "<stringProp name=\"extratag\"></stringProp>\n"
}
1 {
  state = 0;
}

Multi-line match and output in TXR. The rare approach here is to use the data directive to capture two points in the input stream (which is a lazy list of strings) and then the Lisp function ldiff to get the range of lines between those two points (i.e. the block that was matched) so that it can be output with tprint:

@(repeat)
@  (cases)
@    (data start)
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
@    (data end)
@    (do (tprint (ldiff start end)))
@    (output)
<org.apache.jmeter.config.RestServerNodeElement guiclass="TestBeanGUI"
  testclass="org.apache.jmeter.config.RestServerNodeElement"
  testname="ATC_Test_Lab" enabled="true">
<stringProp name="TestPlan.comments"> </stringProp>
<stringProp name="authbasic">${Basic}</stringProp>
<stringProp name="authpassword">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>
<stringProp name="authuser">${User}</stringProp>
<stringProp name="extratag"></stringProp>
@    (end)
@  (or)
@line
@    (do (put-line line))
@  (end)
@(end)

In this approach we could add some variables into the block of code being matched and then refer to them in the @(output). Kind of how like a s/regexp/replace/ can refer to captured pieces via \1, \2, & and so on. Suppose that instead of the static word TestPlan, a piece of text had to be extracted, and the duplicated in all the places where TestPlan appears now:

@(repeat)
@  (cases)
@    (data start)
<stringProp name="@TYPE.user_define_classpath"></stringProp>
</@TYPE>
<hashTree>
@    (data end)
@    (do (tprint (ldiff start end)))
@    (output)
<org.apache.jmeter.config.RestServerNodeElement guiclass="TestBeanGUI"
  testclass="org.apache.jmeter.config.RestServerNodeElement"
  testname="ATC_Test_Lab" enabled="true">
<stringProp name="@TYPE.comments"> </stringProp>
<stringProp name="authbasic">${Basic}</stringProp>
<stringProp name="authpassword">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>
<stringProp name="authuser">${User}</stringProp>
<stringProp name="extratag"></stringProp>
@    (end)
@  (or)
@line
@    (do (put-line line))
@  (end)
@(end)

Let's try this version:

$ txr add.txr -
foo
foo
bar 
bar
blah
blah
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<stringProp name="TestPlan.user_define_classpath"></stringProp>
</TestPlan>
<hashTree>
<org.apache.jmeter.config.RestServerNodeElement guiclass="TestBeanGUI"
  testclass="org.apache.jmeter.config.RestServerNodeElement"
  testname="ATC_Test_Lab" enabled="true">
<stringProp name="TestPlan.comments"> </stringProp>
<stringProp name="authbasic">${Basic}</stringProp>
<stringProp name="authpassword">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>
<stringProp name="authuser">${User}</stringProp>
<stringProp name="extratag"></stringProp>


blah
blah
blah
blah
<stringProp name="EvilPlan.user_define_classpath"></stringProp>  
</EvilPlan>
<hashTree>
<stringProp name="EvilPlan.user_define_classpath"></stringProp>
</EvilPlan>
<hashTree>
<org.apache.jmeter.config.RestServerNodeElement guiclass="TestBeanGUI"
  testclass="org.apache.jmeter.config.RestServerNodeElement"
  testname="ATC_Test_Lab" enabled="true">
<stringProp name="EvilPlan.comments"> </stringProp>
<stringProp name="authbasic">${Basic}</stringProp>
<stringProp name="authpassword">ENC(dsxxxxxxxxxxxWiu+FCzl5+A==)</stringProp>
<stringProp name="authuser">${User}</stringProp>
<stringProp name="extratag"></stringProp>
x
x
y
y
z
z

When TestPlan is changed to EvilPlan, it works nicely, and the code is readable: the multi-line text is represented as-is. If the @ character occurs, it has to be doubled up as @@, but it didn't occur.

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.