1

I have a config file (ssl.cfg) for SSL requests:

oid_section = OIDs

[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn

attributes = v3_req

[OIDs]
OrganizationID = 2.5.4.97        # Don't touch this

[dn]
C = FO
ST = Foobar Monarchy
O = Lorem Ipsum
CN = specific.domain.com
OrganizationID = ABCDE-12345     # Change this
#...

[v3_req]
#...

I'm trying to replace the second OrganizationID's value (ABCDE-12345) with a new value, ABCDE-98765.

The problem is that most matches against OrganizationID conflict between the two identical names, which have to be the same (the one under [OIDs] section is marking a label; the second occurence is actually using the label).

Things that I've tried:

1) Adapted code from this answer (Replace line after match) on Stack Overflow:

REQ_ID="ABCDE-98765"
sed -i "/CN =/!b;n;c\\OrganizationID = ${REQ_ID}" ssl.cfg
  • matches the previous line, CN = ...
  • !b breaks the current match
  • n scans the next line
  • c changes the selected line with text that follows (\ prints a new line; escaped (\\) because of double-quotes)

I don't really like this solution, because of its dependency on the position of the previous line. If someone manually swaps the CN to a different line, this will break.

2) Using the previous attempt's knowledge

REQ_ID="ABCDE-98765"
sed -i "/\\\[dn\\\]/!b;/OrganizationID/c\\OrganizationID = ${REQ_ID}" ssl.cfg

Here, I tried:

  • matching [dn] (double-escaped [ and ])
  • breaking the current match
  • Looking for the next occurence of OrganizationID from that point onward
  • replacing using c\...

This doesn't work; I have a feeling the second match goes from the top all over again. I've noticed that if both occurences are matched, neither seem to change.

I'm specifically looking for a way how to match the OrgID without caring about its position within [dn]. The string ABCDE- in its value will likely not change, but I'd prefer not to depend on it. the OID 2.5.4.97 won't change.

To summarize:

  1. Is there a way to look through a file from a specific line onwards? (using the [dn] tag)
  2. Is there maybe some alternate way how to reach said goal? (awk? other sysutils + pipes? ...)

(my apologies for the confusing title; I don't know how to word it better)

2
  • Welcome to the site. It should be possible to solve your problem with awk. Are the key-value pairs always sepated by "key" "blank" "equal" "blank" "value" or can it be that there is no whitespace around the = sign?
    – AdminBee
    Commented Jul 28, 2020 at 12:19
  • Generally, yes - whitespaces will likely stay there regardless. Ideally, their presence wouldn't matter.
    – Prunebane
    Commented Jul 28, 2020 at 12:29

4 Answers 4

3
$ awk -v org='ABCDE-12345' -v RS= -v ORS='\n\n' '
    $1 == "[dn]" {
        $0 = "\n" $0
        sub(/\nOrganizationID[[:space:]]*=[^\n]*/,"\nOrganizationID = " org)
        sub(/^\n/,"")
    }
    { print }
' file
oid_section = OIDs

[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn

attributes = v3_req

[OIDs]
OrganizationID = 2.5.4.97        # Don't touch this

[dn]
C = FO
ST = Foobar Monarchy
O = Lorem Ipsum
CN = specific.domain.com
OrganizationID = ABCDE-12345
#...

[v3_req]
#...

RS=<null> puts awk into blank-line-separated paragraph mode (see RS = "" in https://www.gnu.org/software/gawk/manual/gawk.html#Multiple-Line), ORS='\n\n' ensures a blank-line separated output, $0 = "\n" $0 ensures every line of the record has a newline before it so that the subsequent sub() using surrounding newlines can succeed even if OrganizationID is the first line of the record and then sub(/^\n/,"") removes the temporary newline we added in the first step.

2
  • I see, so this processing happens only within the [dn] paragraph - I guess I could substitute the literal ABCDE-12345 with another argument variable, like: awk -v RS= -v ORS=\n\n -v MYOUT=${REQ_ID} ' ... "\nOrganizationID = $MYOUT")...' ? Thank you for the hint, will try tomorrow
    – Prunebane
    Commented Jul 28, 2020 at 19:50
  • Yeah, but that's not the right syntax to do that. I just updated my answer to use a variable.
    – Ed Morton
    Commented Jul 28, 2020 at 20:08
3

The following awk code will change the value of the line starting with OrganizationID to whatever you choose, but only in the section [dn], and only on the first occurence of that key:

awk -F'=' -v OFS='=' '$0=="[dn]"{f=1} (f)&&$1~/^OrganizationID/{$2="Something-New"; f=0} 1' ssl.cfg

If you want to avoid hard-coding the replacement, you can import it as in

req_id="Something-New"
awk -F'=' -v OFS='=' -v repl="$req_id" '$0=="[dn]"{f=1} (f)&&$1~/^OrganizationID/{$2=repl; f=0} 1' ssl.cfg

(but note that escape sequences in the variable will be interpreted by awk, so if you need literal backslashes, special precautions are necessary. See this answer on StackOverflow for further reading).

The above code does the following:

  • It considers the = as field separator for input (-F'=') and output (-v OFS='='), so internally, the line will be split at every = into "columns", and the output lines will be assembled by concatenating the field entries with = in between.

  • If the entire current line ($0) only consists of the section header [dn], a flag f will be set to 1 to indicate that the correct section was found.

  • If we are in the correct section, and the first field ($1, i.e. the string before the first =) starts with OrganizationID, then the second field ($2, i.e. the string after the first =) is replaced by the content of the awk variable repl, which we have set to the content of the shell variable $req_id via the -v repl="$req_id" option. It also resets the flag so we don't replace the value for any other occurence of OrganizationID.

  • The "lone" 1 outside of the rules blocks { ... } is a shorthand notation and means "print the current line with all modifications made by the previous rules". The idea behind this mechanism is actually that you can state any condition outside of the rules blocks that, if it evaluates to true (or non-zero), makes awk print the current line (e.g. awk 'FNR==1' would print only the first line, because the internal line counter FNR is equal to 1 only on that line).

    Note that without any explicit print/printf command inside the rule blocks, or the "global" condition evaluating to true, awk will not print the current line.

Output for your example:

oid_section = OIDs

[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn

attributes = v3_req

[OIDs]
OrganizationID = 2.5.4.97        # Don't touch this

[dn]
C = FO
ST = Foobar Monarchy
O = Lorem Ipsum
CN = specific.domain.com
OrganizationID =Something-New
#...

[v3_req]

This code will not modify the file but print the result to the console (so you can check if it is correct first). Once you are satisfied, either redirect the output to a new file and then replace manually, or - if your awk supports it - use the -i inplace extension (works on recent GNU awk implementations).

awk -F"=" -v OFS="=" '.. etc ..' ssl.cfg > ssl.cfg.new

or

awk -i inplace -F"=" -v OFS="=" '.. etc ..' ssl.cfg
1
  • What is the purpose of the 1 at the end of the code string? ... f=0} 1' ssl.cfg
    – Prunebane
    Commented Jul 28, 2020 at 20:06
0

Method1

for  i in `sed -n '/OrganizationID/{;=;}' file| sed -n '2p'`; do sed ''$i's/.*/OrganizationID = ABCDE-98765/g' file; done

Python

#!/usr/bin/python
import re
j=[]
t=0
h=re.compile(r'OrganizationID.*')
k=open('file','r')
for i in k:
    if re.search(h,i):
        t=t+1
        if (t == 2):
            y=re.sub(h,"OrganizationID = ABCDE-98765",i)
            print y.strip()
        else:
            print i.strip()
    else:
        print i.strip()

output

oid_section = OIDs

[req]
default_bits = 4096
prompt = no
default_md = sha256
distinguished_name = dn

attributes = v3_req

[OIDs]
OrganizationID = 2.5.4.97        # Don't touch this

[dn]
C = FO
ST = Foobar Monarchy
O = Lorem Ipsum
CN = specific.domain.com
OrganizationID = ABCDE-98765
5
  • I will admit, I'm having some trouble understanding the code in Method1 - could you explain in a bit more detail? I'm not familiar with the meaning of {;=;} in the very first step, f.e., or why is the for loop necessary (does the loop run once per occurrence? how does it recognize the second one?)
    – Prunebane
    Commented Jul 28, 2020 at 20:22
  • 2
    Copy/paste that first script into shellcheck.net and it'll tell you about the issues we always tell you about with your scripts.
    – Ed Morton
    Commented Jul 29, 2020 at 15:09
  • I am trying to improve my answers @EdMorton Commented Jul 29, 2020 at 15:58
  • 1
    I and others have told you MANY times about using shellcheck.net to check your code, how to use quotes properly (and referred you to mywiki.wooledge.org/Quotes), that backticks are deprecated (and referred you to (mywiki.wooledge.org/BashFAQ/082), not to use for i in to read lines (and referred you to mywiki.wooledge.org/BashFAQ/001), etc. If you're really trying to improve your answers then just follow that advice. Especially given shellcheck.net will tell you about most issues it should be easy not to keep repeating the same problems.
    – Ed Morton
    Commented Jul 29, 2020 at 17:19
  • You could, for example, copy/paste your code into shellcheck.net right now, fix the issues it tells you about, and then edit your answer to show the vastly improved code instead of what you currently have.
    – Ed Morton
    Commented Jul 29, 2020 at 17:29
0

Here is one way to accomplish the task using Awk. We place the REQ_ID onto the environment of awk command line from where it gets available in the builtin associative array ENVIRON

$ REQ_ID="ABCDE-98765" \
   awk '
     BEGIN { a[1] = "OrganizationID = " ENVIRON["REQ_ID"] }
     $1 == "[dn]", match($0, /^OrganizationID[[:blank:]]*=/) {
       a[0] = $0
       $0 = a[RSTART]
     }1
  ' ssl.cfg

With sed there's some more work because we have to ensure that the REQ_ID is placeable on the RHS of the s/// command.

$ REQ_ID="ABCDE-98765"; s="[[:blank:]]" 
$ req_id_esc=$(printf '%s\n' "$REQ_ID" | sed -e 's:[\&/]:\\&:g')
$ sed -e "/^[[]dn]/,/^OrganizationID$s*=/s/^\(OrganizationID\)$s*=.*/\1 = ${req_id_esc}/" ssl.cfg
$ REQ_ID="ABCDE-98765" \
   perl -pale '
      s//$1 = $ENV{REQ_ID}/ if 
        $F[0] eq "[dn]" ... /^(OrganizationID)\s*=.*/ and
        defined $1' ssl.cfg

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .