0

I want to replace a string {Change_me} inside <value>{Change_me}</value> in target.xml using the value I extracted from source.xml

  1. Below are my files
source.xml
    <name>App1</name>
    <value>#!@+aw13dawe=</value>
    <name>App2</name>
    <value>=313ak#a!@BAd</value>
    <name>App3</name>
    <value>!23aaB8=l6</value>
    <name>App4</name>
    <value>0913@aa!#=</value>

target.xml
    <name>App1</name>
    <value>{Change_me}</value>
    <name>App2</name>
    <value>{Change_me}</value>
    <name>App3</name>
    <value>{Change_me}</value>
    <name>App4</name>
    <value>{Change_me}</value>

    forloop file list.txt
    App1
    App2
    App3
    App4

script1 (below the sed works in a linux system but not in SunOS unknown 5.10 Generic_147148-26 i86pc i386 i86pc)

    #!/bin/ksh
    a={Change_me}

    for i in $(cat list.txt) ;
        do sed -i.bak "0,/$a/s//$(grep $i source.xml -A1 | grep value | grep -oP '(?<=value>).*?(?=</value>)')/" target.xml;
    done

Script2 that I wrote in Solaris which I'm having an issue.

    #!/bin/ksh
    a={Change_me}

    for i in $(cat list.txt) ;
        do sed -e "0,/"$a"/s//"$(/usr/sfw/bin/ggrep $i -A1 source.xml | grep value | sed -e 's/.*<value>\(.*\)<\/value>.*/\1/')"" target.xml ;
    done

Results:

# ksh -x change.ksh
/bin/pwd
2> /dev/null
PWD=/scripts/middleware
+ a={Change_me}
+ cat list.txt
+ /usr/sfw/bin/ggrep App1 -A1 source.xml
+ grep value
+ sed -e s/.*<value>\(.*\)<\/value>.*/\1/
+ sed -e 0,/{Change_me}/s//#!@+aw13dawe= target.xml
sed: command garbled: 0,/{Change_me}/s//#!@+aw13dawe=
+ /usr/sfw/bin/ggrep App2 -A1 source.xml
+ grep value
+ sed -e s/.*<value>\(.*\)<\/value>.*/\1/
+ sed -e 0,/{Change_me}/s//=313ak#a!@BAd target.xml
sed: command garbled: 0,/{Change_me}/s//=313ak#a!@BAd
+ /usr/sfw/bin/ggrep App3 -A1 source.xml
+ grep value
+ sed -e s/.*<value>\(.*\)<\/value>.*/\1/
+ sed -e 0,/{Change_me}/s//!23aaB8=l6 target.xml
sed: command garbled: 0,/{Change_me}/s//!23aaB8=l6
+ /usr/sfw/bin/ggrep App4 -A1 source.xml
+ grep value
+ sed -e s/.*<value>\(.*\)<\/value>.*/\1/
+ sed -e 0,/{Change_me}/s//0913@aa!#= target.xml
sed: command garbled: 0,/{Change_me}/s//0913@aa!#=

sed -i is not working in solaris, that's why I used sed -e, but as shown in the result it does not work as expected, your help and advice is highly appreciated.

Here is the good case output Note: Only Works on Linux (RHEL 7 - testmachine)

[root@vmserver1 ~]# bash -x change.sh
+ a='{Change_me}'
++ cat list.txt
+ for i in '$(cat list.txt)'
++ grep App1 source.xml -A1
++ grep value
++ grep -oP '(?<=value>).*?(?=</value>)'
+ sed -i.bak '0,/{Change_me}/s//#!@+aw13dawe=/' target.xml
+ for i in '$(cat list.txt)'
++ grep App2 source.xml -A1
++ grep value
++ grep -oP '(?<=value>).*?(?=</value>)'
+ sed -i.bak '0,/{Change_me}/s//=313ak#a!@BAd/' target.xml
+ for i in '$(cat list.txt)'
++ grep App3 source.xml -A1
++ grep value
++ grep -oP '(?<=value>).*?(?=</value>)'
+ sed -i.bak '0,/{Change_me}/s//!23aaB8=l6/' target.xml
+ for i in '$(cat list.txt)'
++ grep App4 source.xml -A1
++ grep value
++ grep -oP '(?<=value>).*?(?=</value>)'
+ sed -i.bak '0,/{Change_me}/s//0913@aa!#=/' target.xml
[root@vmserver1 ~]#

Good Result of target.xml

[root@vmserver1 ~]# cat target.xml
<name>App1</name>
<value>#!@+aw13dawe=</value>
<name>App2</name>
<value>=313ak#a!@BAd</value>
<name>App3</name>
<value>!23aaB8=l6</value>
<name>App4</name>
<value>0913@aa!#=</value>
Pitzdroid
  • 1
  • 1
  • 1
    Please show the output of `ksh -x change.ksh` both for the good case and the bad case. Explaining your algorithm might also help. Do I understand correct that you are trying to extract the string between `` and `` and construct an `sed` command that will replace exactly this string by `{Change_me}`? Is the file `source.xml` complete or does your real file contain more data? In the latter case show an example that better represents your real input. Please [edit] your question to answer. – Bodo Jul 27 '21 at 07:26
  • I think the `ksh -x` output shows the bad case only. It would be good to also have the good case to compare with – Bodo Jul 27 '21 at 07:36
  • Hi Bodo, the bad case is within the post, my main objective here is I want to replace the string `{Change_me}` inside tags of `{Change_me}` which exist in the target.xml, the source.xml is my input variable to replace {Change_me} , as you notice there a assigned value for each name that is why I'm using grep -A1 to assigned the appropriate value based on the name, its like copying the appropriate password from source.xml to target.xml. I hope my explanation is clear, will post later the good case so we can have better understanding on the expected output. thank you very much. – Pitzdroid Jul 27 '21 at 07:44
  • Hi Bodo, I added the good case in the post, since can't add here in the comment, thank you very much. – Pitzdroid Jul 27 '21 at 07:53
  • [Do not parse xml with regex](https://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags). Do you want to match the `{this}` with the matching between source.xml and target.xml files? Or do you want to just replace first {change} in target.xml for first from source.xml file, second for second, etc.?. – KamilCuk Jul 27 '21 at 12:16
  • Hi KamilCuk, I have no experience other than using unix/linux command, I decided to replace the first {change_me} by depending on the list of for loop, meaning I will search first the App? in the source so I can get the proper .?* and assign it to the target.xml , the situation is I have this target.xml with many lines or other attributes or tags, there is a part there for server information that when developer produce this target.xml, they don't know the password that's why they put {change_me} , so manual searching that correct password from source takes time. – Pitzdroid Jul 27 '21 at 13:46
  • Hi KamilCuk, note that target.xml are not always has {change_me} ,Sometimes I have 20 servers in the target.xml and only 10 servers of them has {change_me} then those 10 servers are only my target, also note that the tags Server has always next line tags which is Password , so that's the reason why command grep -A1 (After) is used in this script, the list.txt are servers parse from another script orderly from target.xml that is the reason why the 1st occurrence is to be replaced. – Pitzdroid Jul 27 '21 at 14:01
  • `the situation is I have this target.xml with many lines or other attributes or tags` Then, you should _strongly_ consider using an XML-aware parser. Using regex to parse XML is just not feasible and just does not work. Perl and python are popular scripting languages, `xmlstarlet` `xmllint` are popular C programs to parse xml. – KamilCuk Jul 27 '21 at 22:03
  • `bash -x change.sh` are you using bash _or_ ksh? If you are using ksh, why test with bash? – KamilCuk Jul 27 '21 at 22:15
  • Hi KamilChuk, I created two script one for bash and one for solaris, the target server is solaris, as of now to deliver the desired output I use the bash shell under a linux vm created under oracle virtual box, where I can change all those {change_me} and meet the expected results. downside is since this is not a official server(linux vm) I need to download from solaris then process on my vm then download and upload to official server, it is really very much appreciated if I just can transfer those parse password to replace the constant string inside this tag `{Change_me}. – Pitzdroid Jul 29 '21 at 09:20
  • Does this answer your question? [Replace a value in XML](https://stackoverflow.com/questions/28278865/replace-a-value-in-xml) – Romeo Ninov Aug 03 '21 at 17:46

3 Answers3

1

When you look at sed -e 0,/{Change_me}/s//#!@+aw13dawe= target.xml, you will notice that you are missing the closing /.
So change

sed -e "0,/"$a"/s//"$(/usr/sfw/bin/ggrep $i -A1 source.xml |
  grep value |
  sed -e 's/.*<value>\(.*\)<\/value>.*/\1/')"" target.xml

into

sed -e "0,/"$a"/s//"$(/usr/sfw/bin/ggrep $i -A1 source.xml |
  grep value |
  sed -e 's/.*<value>\(.*\)<\/value>.*/\1/')"/" target.xml

However, this will fail with strange passwords, don't generate sed commands with sed when random strings are possible.
When you want a solution with sed and not a xml-parser, you will have to fight against all those small exceptions that require huge modifications. For a short while you may limit the acceptable passwords and use the above solution, which might give you time to search for a better tool.
In your solution you also must be sure, that list.txt has all the entries with {change_me} in the same order as the target.xml.
When you still don't want to learn a xml-browser, than consider using awk. With awk you can read 2 files, make an array with the names/values and use that array for the second. Give it a try and post a new question when you are stuck.

Walter A
  • 19,067
  • 2
  • 23
  • 43
  • Hi @Walter A, I'm not sure what I'm still missing , it did not throw an error but no changes in my file here is the some output ```# ksh -x change.ksh /bin/pwd 2> /dev/null PWD=/scripts/middleware + a={Change_me} + cat list.txt + /usr/sfw/bin/ggrep App1 -A1 source.xml + grep value + sed -e s/.*\(.*\)<\/value>.*/\1/ + sed -e 0,/{Change_me}/s//#!@+aw13dawe=/ target.xml App1 {Change_me} App2 ``` – Pitzdroid Jul 29 '21 at 09:33
  • I am going to delete this answer when it doesn't help. Try to split your command into smaller steps and look what step is failing. Perhaps use `printf "%s\n" {7..14} | sed '0,/1/ s//changed /'` for some basic tests. – Walter A Jul 29 '21 at 12:28
  • Hi @Walter A, seems not working on Solaris , here is the output # printf "%s\n" {7..14} | sed '0,/1/ s//changed /' {7..14} # uname -a SunOS unknown 5.10 Generic_147148-26 i86pc i386 i86pc, but works on linux [root@vmserver1 ~]# printf "%s\n" {1..4} | sed '0,/1/ s//changed /' changed 2 3 4 – Pitzdroid Jul 29 '21 at 13:21
  • Than the `sed` version on Solaris seems to work different, perhaps `printf "%s\n" {7..14} | sed '0,/1/ s/1/changed /'` (I added 1 to the sed command) works. First test this before you try to fix the complete solution (`sed -e "0,/"$a"/s/"$a"/"...`) – Walter A Jul 29 '21 at 13:58
  • Hi @Walter A, I guess I need to surrender this sed in Solaris :), I tried the suggested command and does not work , I tried different approach by changing sed -e "0 to 1" where I figured out that this number is being interpreted first before the matching pattern, meaning If I start at index 0 and did not found the pattern it will not scan the next index that's why no output change, so when I change it to 1 it was able to replace the text, but since this is a for loop using list.txt it will just repeatedly change App1 Value, I agree with KamilCuk not good idea to parse xml using sed. – Pitzdroid Jul 30 '21 at 12:26
0

The following POSIX script with comments blatantly ignores that the input file has XML format and strongly assumes a specific format of the files. The assumption is that the files are line based, they consist of lines <name>something</name> and <value>something</value> one after another.

#!/bin/sh

cat > source.xml <<EOF
    <name>App1</name>
    <value>#!@+aw13dawe=</value>
    <name>App2</name>
    <value>=313ak#a!@BAd</value>
    <name>App3</name>
    <value>!23aaB8=l6</value>
    <name>App4</name>
    <value>0913@aa!#=</value>
EOF
cat > target.xml <<EOF
    <name>App1</name>
    <value>{Change_me}</value>
    <name>App2</name>
    <value>{Change_me}</value>
    <name>App3</name>
    <value>{Change_me}</value>
    <name>App4</name>
    <value>{Change_me}</value>
EOF

# Load target to memory.
data=$(cat target.xml)

# Let's preprocess source.xml to a CSV file separated with tabs
# This assumes strict format of source.xml
# I am using space for s command separator.
source=$(sed 'N;s .*<name>\(.*\)</name>.*<value>\(.*\)</value>.* \1\t\2 ' source.xml)
# For each name
data=$(
    printf "%s\n" "$source" | {
        while IFS=$(printf '\t') read -r name value; do
            # Find the line number taht contains the name tag
            if ! nameline=$(
                     printf "%s\n" "$data" |
                     grep -nF "<name>$name</name>" |
                     cut -d: -f1
                   ) || [ -z "$nameline" ]; then
                # Handle name not found
                continue
            fi
            # We are strongly assuming we are editing the next line.
            editline=$((nameline + 1))
            # Escape sed pattern.
            # https://stackoverflow.com/questions/407523/escape-a-string-for-a-sed-replace-pattern
            valueesc=$(printf "%s\n" "$value" | sed -e 's/[\/&]/\\&/g')
            # edit the line with sed.
            data=$(printf "%s\n" "$data" | sed "$editline"'s \(<value>\)[^>]*\(</value>\) \1'"$valueesc"'\2 ')
        done
        printf "%s\n" "$data"
    }
)

# Display modified file content
printf "%s\n" "$data"

The script outputs:

    <name>App1</name>
    <value>#!@+aw13dawe=</value>
    <name>App2</name>
    <value>=313ak#a!@BAd</value>
    <name>App3</name>
    <value>!23aaB8=l6</value>
    <name>App4</name>
    <value>0913@aa!#=</value>
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
0

A solution with awk that might work for your situation:

awk -F '[<>]' '
  NR==FNR && $2 == "name" { lastname=$3 }
  NR==FNR && $2 == "value" { pw[lastname]=$3 }
  NR>FNR && $2 == "name" { lastname=$3; print }
  NR>FNR && $2 == "value" { printf("<value>%s</value>\n", pw[lastname]) }
  ' source.xml target.xml

When you have additional wishes, try to change the script and post a new question when you are stuck. Possible improvements are:

  • Only change the value when $3 == "{Change_me}"
  • (complicated) first read list.txt and only change values when the name is found in the changes[].
  • Redesign, making it possible that you have different tags on one line.
  • Repair the bug, that a password with < or > will fail (check NF).
Walter A
  • 19,067
  • 2
  • 23
  • 43