Replace first match after a match with sed - bash

what I want to achieve is to find an string in file and go on finding first occurrence of another match and replace that with some value.
Ex: string = {Name: name;Address:someadd;var1:var1;var2:var2},{Name: differntName;Address:someadd;var1:var1;var2:var2}
Now what i need to do is to find Name: name and then find first occurrence of "var2:var2" after "Name: name" and replace it with "var2:newvarvalue"
Please note that i need to complete this task with sed in bash scripting.
Thanks in advance.
Edit : i am trying to modify .yaml docker compose file

Here is a terrible solution using sed :
split on ,, one part per line :
sed 's/,/\n/g'
replace var2:var2 by var2:newvarvalue if on the same line as Name: name :
sed '/Name: name/s/var2:var2/var2:newvarvalue/'
or
sed -E 's/(Name: name.*)var2:var2/\1var2:newvarvalue/'
It's terrible because any extra comma or linefeed might break the whole thing.

var='{Name: name;Address:somea,dd;var1:var1;var2:var2},{Name: differntName;Address:someadd;var1:var1;var2:var2}'
NEW_VAL='new_value'
IFS=$'\n'
OBJECTS=("$(echo "${var}" | sed -nE 's/[^{]*(\{[^}]+\})/\1\n/gp')")
for obj in "${OBJECTS[#]}"; do
echo "${obj}" | sed -E 's/(.*var2:)(var2)(.*)/\1'"${NEW_VAL}"'\3/'
done
Output:
{Name: name;Address:somea,dd;var1:var1;var2:new_value}
{Name: differntName;Address:someadd;var1:var1;var2:new_value}
This solution accounts for a comma in the object (as exemplified in the first object) by extracting each and setting the delimiter to a newline.

Related

sed replace string with pipe and stars

I have the following string:
|**barak**.version|2001.0132012031539|
in file text.txt.
I would like to replace it with the following:
|**barak**.version|2001.01.2012031541|
So I run:
sed -i "s/\|\*\*$module\*\*.version\|2001.0132012031539/|**$module**.version|$version/" text.txt
but the result is a duplicate instead of replacing:
|**barak**.version|2001.01.2012031541|**barak**.version|2001.0132012031539|
What am I doing wrong?
Here is the value for module and version:
$ echo $module
barak
$ echo $version
2001.01.2012031541
Assumptions:
lines of interest start and end with a pipe (|) and have one more pipe somewhere in the middle of the data
search is based solely on the value of ${module} existing between the 1st/2nd pipes in the data
we don't know what else may be between the 1st/2nd pipes
the version number is the only thing between the 2nd/3rd pipes
we don't know the version number that we'll be replacing
Sample data:
$ module='barak'
$ version='2001.01.2012031541'
$ cat text.txt
**barak**.version|2001.0132012031539| <<<=== leave this one alone
|**apple**.version|2001.0132012031539|
|**barak**.version|2001.0132012031539| <<<=== replace this one
|**chuck**.version|2001.0132012031539|
|**barak**.peanuts|2001.0132012031539| <<<=== replace this one
One sed solution with -Extended regex support enabled and making use of a capture group:
$ sed -E "s/^(\|[^|]*${module}[^|]*).*/\1|${version}|/" text.txt
Where:
\| - first occurrence (escaped pipe) tells sed we're dealing with a literal pipe; follow-on pipes will be treated as literal strings
^(\|[^|]*${module}[^|]*) - first capture group that starts at the beginning of the line, starts with a pipe, then some number of non-pipe characters, then the search pattern (${module}), then more non-pipe characters (continues up to next pipe character)
.* - matches rest of the line (which we're going to discard)
\1|${version}| - replace line with our first capture group, then a pipe, then the new replacement value (${version}), then the final pipe
The above generates:
**barak**.version|2001.0132012031539|
|**apple**.version|2001.0132012031539|
|**barak**.version|2001.01.2012031541| <<<=== replaced
|**chuck**.version|2001.0132012031539|
|**barak**.peanuts|2001.01.2012031541| <<<=== replaced
An awk alternative using GNU awk:
awk -v mod="$module" -v vers="$version" -F \| '{ OFS=FS;split($2,map,".");inmod=substr(map[1],3,length(map[1])-4);if (inmod==mod) { $3=vers } }1' file
Pass two variables mod and vers to awk using $module and $version. Set the field delimiter to |. Split the second field into array map using the split function and using . as the delimiter. Then strip the leading and ending "**" from the first index of the array to expose the module name as inmod using the substr function. Compare this to the mod variable and if there is a match, change the 3rd delimited field to the variable vers. Print the lines with short hand 1
Pipe is only special when you're using extended regular expressions: sed -E
There's no reason why you need extended here, stick with basic regex:
sed "
# for lines matching module.version
/|\*\*$module\*\*.version|/ {
# replace the version
s/|2001.0132012031539|/|$version|/
}
" text.txt
or as an unreadable one-liner
sed "/|\*\*$module\*\*.version|/ s/|2001.0132012031539|/|$version|/" text.txt

How to use sed command with regular expression?

I have an input file (PowerCenter xml file) and I need to replace a shortcut for a target environment. So I need to find a line with SHORTCUT and then change a parameter in REPOSITORYNAME
I am trying to find the substring from REPOSITORYNAME to eol and replace the first occurrence of "whatever" between quotes. I have found a regex ".*?" which works in regexr.com, but not in bash
input=SHORTCUT COMMENTS="" REFERENCETYPE="LOCAL" REFOBJECTNAME="mplt_EBXSOAPExport" REPOSITORYNAME="ZRH_PCE_P01" VERSIONNUMBER="1"/>"
x=$(echo $input | grep -o 'REPOSITORYNAME.*>' | sed -r '{0,/\".\+?\"/s/\".\+?\"/\"TARGET\"/}')
echo $x;
input="SHORTCUT COMMENTS="" REFERENCETYPE="LOCAL" REFOBJECTNAME="mplt_EBXSOAPExport" REPOSITORYNAME="any_word" VERSIONNUMBER="1"/>"
another possible input="SHORTCUT COMMENTS="" REFERENCETYPE="LOCAL" REFOBJECTNAME="mplt_EBXSOAPExport" REPOSITORYNAME = "any_word" VERSIONNUMBER="1"/>"
output="SHORTCUT COMMENTS="" REFERENCETYPE="LOCAL" REFOBJECTNAME="mplt_EBXSOAPExport" REPOSITORYNAME="updated" VERSIONNUMBER="1"/>"
Thanks
You can use the following :
sed '/SHORTCUT/s/REPOSITORYNAME\s*=\s*"[^"]*"/REPOSITORYNAME="WhatYouWant"/'
It tests that a line contains SHORTCUT and when it does it performs a search/replace matching the REPOSITORYNAME and its value and replacing the latter by a new one.
You can try it here !

bash script: how to insert text between two specific characters

For example, I have a file containing a line as below:
"abc":"def"
I need to insert 123 between "abc":" and def" so that the line will become: "abc":"123def".
As "abc" appears only once so I think I can just search it and do the insertion.
How to do this with bash script such as sed or awk?
AMD$ sed 's/"abc":"/&123/' File
"abc":"123def"
Match "abc":", then append this match with 123 (& will contain the matched string "abc":")
If you want to take care of space before and after :, you can use:
sed 's/"abc" *: *"/&123/'
For replacing all such patterns, use g with sed.
sed 's/"abc" *: *"/&123/g' File
sed:
$ sed -E 's/(:")(.*)/\1123\2/' <<<'"abc":"def"'
"abc":"123def"
(:") gets :" and put in captured group 1
(.*) gets the remaining portion and put in captured group 2
in the replacement, \1123\2 puts 123 between the groups
awk:
$ awk -F: 'sub(".", "&123", $2)' <<<'"abc":"def"'
"abc" "123def"
In the sub() function, the second ($2) field is being operated on, pattern is used as . (which would match "), and in the replacement the matched portion (&) is followed by 123.
echo '"abc":"def"'| awk '{sub(/def/,"123def")}1'
"abc":"123def"

Bash matching part of string

Say I have a string like
s1="sxfn://xfn.oxbr.ac.uk:8843/xfn/mech2?XFN=/castor/
xf.oxbr.ac.uk/prod/oxbr.ac.uk/disk/xf20.m.ac.uk/prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst"
or
s2="sxfn://xfn.gla.ac.uk:8841/xfn/mech2?XFN=/castor/
xf.gla.ac.uk/space/disk1/prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst"
and I want in my script to extract the last part starting from prod/ i.e. "prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst". Note that $s1 contains two occurrences of "prod/".
What is the most elegant way to do this in bash?
Using BASH string manipulations you can do:
echo "prod/${s1##*prod/}"
prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst
echo "prod/${s2##*prod/}"
prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst
With awk (which is a little overpowered for this, but it may be helpful if you have a file full of these strings you need to parse:
echo "sxfn://xfn.gla.ac.uk:8841/xfn/mech2?XFN=/castor/xf.gla.ac.uk/space/disk1/prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst" | awk -F"\/prod" '{print "/prod"$NF}'
That's splitting the string by '/prod' then printing out the '/prod' delimiter and the last token in the string ($NF)
sed can do it nicely:
s1="sxfn://xfn.oxbr.ac.uk:8843/xfn/mech2?XFN=/castor/xf.oxbr.ac.uk/prod/oxbr.ac.uk/disk/xf20.m.ac.uk/prod/v1.8/pienug_ib-2/reco_c21_dr3809_r35057.dst"
echo "$s1" | sed 's/.*\/prod/\/prod/'
this relies on the earger matching of the .* part up front.

How to get delete word combination "Name Server:" without quotes but keep 'Name Server:someletters/digits' in sed

I have the following lines:
Name Server:NS92.WORLDNIC.COM(or some other value)
Name Server:
Name Server:
Name Server:
Please see the screenshot for better understanding: http://imgur.com/q6Ir4lo
How do I get rid of the 'Name Server:' line but keep the line with the value?
I tried /Name Server:{0,0}/d but it deletes all lines.
Thanks
I was able to get the following two lines to work:
I believe the [:space:] is POSIX compliant:
cat test |sed '/^Name Server:[[:space:] \t]\?$/d'
An alternative is simply:
cat test |sed '/^Name Server:[ \t]\?$/d'
I've also found in sed, that most of the meta-characters (eg + ? ) need to be escaped for sed to recognize them correctly.
This works for me:
echo "Name Server:NS92.WORLDNIC.COM" | sed 's/^Name Server://'
cut -d ":" -f 2 < ff | sed '/^$/d'
Uses ':' as delimiter and splits the line (-d option), then selects the second field (-f option)

Resources