replace string inside `<value> REPLACE ANY STRING</value>` for solaris 10 UNIX command

270 Views Asked by At

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>
3

There are 3 best solutions below

5
Walter A On

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.

0
KamilCuk On

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>
0
Walter A On

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).